From 4fea8f409dd0baa921c41b09a8f2d87dfa269233 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 16 May 2024 23:06:02 +0200 Subject: [PATCH] fix: make `@uploadthing/shared` treeshakeable (#808) --- .changeset/beige-bats-press.md | 8 ++ examples/with-novel/package.json | 4 +- packages/shared/src/effect.ts | 22 +-- packages/shared/src/error.ts | 7 +- packages/shared/src/tagged-errors.ts | 52 ++++--- packages/uploadthing/src/client.ts | 2 +- packages/uploadthing/src/internal/dev-hook.ts | 11 +- packages/uploadthing/src/internal/handler.ts | 12 +- .../src/internal/multi-part.browser.ts | 4 +- .../src/internal/multi-part.server.ts | 8 +- .../src/internal/presigned-post.browser.ts | 3 +- .../src/internal/presigned-post.server.ts | 6 +- .../src/internal/shared-schemas.ts | 129 +++++++++--------- .../src/internal/to-web-request.ts | 6 +- packages/uploadthing/src/internal/types.ts | 12 +- .../src/internal/validate-request-input.ts | 19 ++- packages/uploadthing/src/sdk/utils.ts | 10 +- packages/uploadthing/test/__test-helpers.ts | 4 +- .../uploadthing/test/request-handler.test.ts | 14 +- pnpm-lock.yaml | 63 +-------- 20 files changed, 188 insertions(+), 208 deletions(-) create mode 100644 .changeset/beige-bats-press.md diff --git a/.changeset/beige-bats-press.md b/.changeset/beige-bats-press.md new file mode 100644 index 0000000000..3339fb80f6 --- /dev/null +++ b/.changeset/beige-bats-press.md @@ -0,0 +1,8 @@ +--- +"uploadthing": patch +"@uploadthing/shared": patch +--- + +fix treeshakeability of `Effect` dependency by avoiding top-level function calls, and falling back to `#__PURE__` directives otherwise + +Importing some utility from e.g. `@uploadthing/shared` should not explode bundle if `Effect` isn't used for other stuff diff --git a/examples/with-novel/package.json b/examples/with-novel/package.json index dbe54b2a1f..1e177df8b1 100644 --- a/examples/with-novel/package.json +++ b/examples/with-novel/package.json @@ -13,7 +13,7 @@ "@radix-ui/react-popover": "^1.0.6", "@radix-ui/react-slot": "^1.0.2", "@tailwindcss/typography": "^0.5.10", - "@uploadthing/react": "^6.5.3", + "@uploadthing/react": "^6.5.4", "class-variance-authority": "^0.7.0", "cmdk": "^0.2.1", "lucide-react": "^0.368.0", @@ -24,7 +24,7 @@ "react-dom": "18.2.0", "sonner": "^1.4.41", "tailwind-merge": "^2.2.1", - "uploadthing": "6.10.3", + "uploadthing": "6.10.4", "use-debounce": "^9.0.3" }, "devDependencies": { diff --git a/packages/shared/src/effect.ts b/packages/shared/src/effect.ts index f7f53ea87e..22b4b1a266 100644 --- a/packages/shared/src/effect.ts +++ b/packages/shared/src/effect.ts @@ -16,10 +16,11 @@ export type FetchContextService = { "x-uploadthing-be-adapter": string | undefined; }; }; -export class FetchContext extends Context.Tag("uploadthing/FetchContext")< - FetchContext, - FetchContextService ->() {} +export class FetchContext + extends /** #__PURE__ */ Context.Tag("uploadthing/FetchContext")< + FetchContext, + FetchContextService + >() {} interface ResponseWithURL extends ResponseEsque { requestUrl: string; @@ -99,9 +100,10 @@ export const parseRequestJson = (req: Request) => * Schedule that retries with exponential backoff, up to 1 minute. * 10ms * 4^n, where n is the number of retries. */ -export const exponentialBackoff = pipe( - Schedule.exponential(Duration.millis(10), 4), // 10ms, 40ms, 160ms, 640ms... - Schedule.andThenEither(Schedule.spaced(Duration.seconds(1))), - Schedule.compose(Schedule.elapsed), - Schedule.whileOutput(Duration.lessThanOrEqualTo(Duration.minutes(1))), -); +export const exponentialBackoff = () => + pipe( + Schedule.exponential(Duration.millis(10), 4), // 10ms, 40ms, 160ms, 640ms... + Schedule.andThenEither(Schedule.spaced(Duration.seconds(1))), + Schedule.compose(Schedule.elapsed), + Schedule.whileOutput(Duration.lessThanOrEqualTo(Duration.minutes(1))), + ); diff --git a/packages/shared/src/error.ts b/packages/shared/src/error.ts index 31c64f41b5..7230c87eba 100644 --- a/packages/shared/src/error.ts +++ b/packages/shared/src/error.ts @@ -1,4 +1,4 @@ -import { TaggedError } from "effect/Data"; +import * as Data from "effect/Data"; import type { Json } from "./types"; import { isObject } from "./utils"; @@ -58,7 +58,10 @@ export interface SerializedUploadThingError { export class UploadThingError< TShape extends Json = { message: string }, -> extends TaggedError("UploadThingError")<{ message: string }> { +> extends Data.Error<{ message: string }> { + readonly _tag = "UploadThingError"; + readonly name = "UploadThingError"; + public readonly cause?: unknown; public readonly code: ErrorCode; public readonly data: TShape | undefined; diff --git a/packages/shared/src/tagged-errors.ts b/packages/shared/src/tagged-errors.ts index 2ec8a196d1..90899e33eb 100644 --- a/packages/shared/src/tagged-errors.ts +++ b/packages/shared/src/tagged-errors.ts @@ -1,10 +1,11 @@ -import { TaggedError } from "effect/Data"; +import * as Data from "effect/Data"; +import * as Predicate from "effect/Predicate"; -import { isObject } from "./utils"; - -export class InvalidRouteConfigError extends TaggedError("InvalidRouteConfig")<{ +export class InvalidRouteConfigError extends Data.Error<{ reason: string; }> { + readonly _tag = "InvalidRouteConfig"; + readonly name = "InvalidRouteConfigError"; constructor(type: string, field?: string) { const reason = field ? `Expected route config to have a ${field} for key ${type} but none was found.` @@ -13,42 +14,53 @@ export class InvalidRouteConfigError extends TaggedError("InvalidRouteConfig")<{ } } -export class UnknownFileTypeError extends TaggedError("UnknownFileType")<{ +export class UnknownFileTypeError extends Data.Error<{ reason: string; }> { + readonly _tag = "UnknownFileType"; + readonly name = "UnknownFileTypeError"; constructor(fileName: string) { const reason = `Could not determine type for ${fileName}`; super({ reason }); } } -export class InvalidFileTypeError extends TaggedError("InvalidFileType")<{ +export class InvalidFileTypeError extends Data.Error<{ reason: string; }> { + readonly _tag = "InvalidFileType"; + readonly name = "InvalidFileTypeError"; constructor(fileType: string, fileName: string) { const reason = `File type ${fileType} not allowed for ${fileName}`; super({ reason }); } } -export class InvalidFileSizeError extends TaggedError("InvalidFileSize")<{ +export class InvalidFileSizeError extends Data.Error<{ reason: string; }> { + readonly _tag = "InvalidFileSize"; + readonly name = "InvalidFileSizeError"; constructor(fileSize: string) { const reason = `Invalid file size: ${fileSize}`; super({ reason }); } } -export class InvalidURLError extends TaggedError("InvalidURL")<{ +export class InvalidURLError extends Data.Error<{ reason: string; }> { + readonly _tag = "InvalidURL"; + readonly name = "InvalidURLError"; constructor(attemptedUrl: string) { super({ reason: `Failed to parse '${attemptedUrl}' as a URL.` }); } } -export class RetryError extends TaggedError("RetryError") {} +export class RetryError extends Data.Error { + readonly _tag = "RetryError"; + readonly name = "RetryError"; +} /** * @internal @@ -60,7 +72,7 @@ export const getRequestUrl = (input: RequestInfo | URL) => { return input.toString(); }; -export class FetchError extends TaggedError("FetchError")<{ +export class FetchError extends Data.Error<{ readonly input: { url: string; method: string | undefined; @@ -68,22 +80,28 @@ export class FetchError extends TaggedError("FetchError")<{ headers: Record; }; readonly error: unknown; -}> {} +}> { + readonly _tag = "FetchError"; + readonly name = "FetchError"; +} -export class InvalidJsonError extends TaggedError("InvalidJsonError")<{ +export class InvalidJsonError extends Data.Error<{ readonly input: unknown; readonly error: unknown; -}> {} +}> { + readonly _tag = "InvalidJsonError"; + readonly name = "InvalidJsonError"; +} -export class BadRequestError extends TaggedError( - "BadRequestError", -)<{ +export class BadRequestError extends Data.Error<{ readonly message: string; readonly status: number; readonly json: T; }> { + readonly _tag = "BadRequestError"; + readonly name = "BadRequestError"; getMessage() { - if (isObject(this.json)) { + if (Predicate.isRecord(this.json)) { if (typeof this.json.message === "string") return this.json.message; } return this.message; diff --git a/packages/uploadthing/src/client.ts b/packages/uploadthing/src/client.ts index 4777a340b3..8eacb4d3f1 100644 --- a/packages/uploadthing/src/client.ts +++ b/packages/uploadthing/src/client.ts @@ -237,7 +237,7 @@ const uploadFile = < Effect.map(({ callbackData }) => callbackData), Effect.retry({ while: (res) => res instanceof RetryError, - schedule: exponentialBackoff, + schedule: exponentialBackoff(), }), Effect.when(() => !opts.skipPolling), Effect.map(Option.getOrNull), diff --git a/packages/uploadthing/src/internal/dev-hook.ts b/packages/uploadthing/src/internal/dev-hook.ts index f313ee2ab4..cbaeb42cdf 100644 --- a/packages/uploadthing/src/internal/dev-hook.ts +++ b/packages/uploadthing/src/internal/dev-hook.ts @@ -12,8 +12,7 @@ import { } from "@uploadthing/shared"; import type { ResponseEsque } from "@uploadthing/shared"; -import { PollUploadResponseSchema } from "./shared-schemas"; -import type { UploadedFileData } from "./shared-schemas"; +import { PollUploadResponse, UploadedFileData } from "./shared-schemas"; const isValidResponse = (response: ResponseEsque) => { if (!response.ok) return false; @@ -29,7 +28,7 @@ export const conditionalDevServer = (fileKey: string, apiKey: string) => { generateUploadThingURL(`/api/pollUpload/${fileKey}`), ).pipe( Effect.andThen(parseResponseJson), - Effect.andThen(S.decodeUnknown(PollUploadResponseSchema)), + Effect.andThen(S.decodeUnknown(PollUploadResponse)), Effect.andThen((res) => res.status === "done" ? Effect.succeed(res.fileData) @@ -37,7 +36,7 @@ export const conditionalDevServer = (fileKey: string, apiKey: string) => { ), Effect.retry({ while: (err) => err instanceof RetryError, - schedule: exponentialBackoff, + schedule: exponentialBackoff(), }), Effect.catchTag("RetryError", (e) => Effect.die(e)), ); @@ -61,14 +60,14 @@ export const conditionalDevServer = (fileKey: string, apiKey: string) => { const payload = JSON.stringify({ status: "uploaded", metadata: JSON.parse(file.metadata ?? "{}") as unknown, - file: { + file: new UploadedFileData({ url: `https://utfs.io/f/${encodeURIComponent(fileKey)}`, key: fileKey, name: file.fileName, size: file.fileSize, customId: file.customId, type: file.fileType, - } satisfies UploadedFileData, + }), }); const signature = yield* Effect.tryPromise({ diff --git a/packages/uploadthing/src/internal/handler.ts b/packages/uploadthing/src/internal/handler.ts index d13b5a313e..e0d7523fa5 100644 --- a/packages/uploadthing/src/internal/handler.ts +++ b/packages/uploadthing/src/internal/handler.ts @@ -25,10 +25,10 @@ import { resolveCallbackUrl } from "./resolve-url"; import { FailureActionPayload, MultipartCompleteActionPayload, - PresignedURLResponseSchema, - ServerCallbackPostResponseSchema, + PresignedURLResponse, + ServerCallbackPostResponse, UploadActionPayload, - UploadedFileDataSchema, + UploadedFileData, } from "./shared-schemas"; import type { FileRouter, @@ -169,7 +169,7 @@ const handleCallbackRequest = Effect.gen(function* () { S.decodeUnknown( S.Struct({ status: S.String, - file: UploadedFileDataSchema, + file: UploadedFileData, metadata: S.Record(S.String, S.Unknown), }), ), @@ -211,7 +211,7 @@ const handleCallbackRequest = Effect.gen(function* () { headers: { "Content-Type": "application/json" }, }).pipe( Effect.andThen(parseResponseJson), - Effect.andThen(S.decodeUnknown(ServerCallbackPostResponseSchema)), + Effect.andThen(S.decodeUnknown(ServerCallbackPostResponse)), ); return { body: null }; }); @@ -370,7 +370,7 @@ const handleUploadAction = Effect.gen(function* () { }, ).pipe( Effect.andThen(parseResponseJson), - Effect.andThen(S.decodeUnknown(PresignedURLResponseSchema)), + Effect.andThen(S.decodeUnknown(PresignedURLResponse)), ); yield* Effect.logDebug("UploadThing responded with:", presignedUrls); diff --git a/packages/uploadthing/src/internal/multi-part.browser.ts b/packages/uploadthing/src/internal/multi-part.browser.ts index 69a39133a7..187ca13fed 100644 --- a/packages/uploadthing/src/internal/multi-part.browser.ts +++ b/packages/uploadthing/src/internal/multi-part.browser.ts @@ -9,7 +9,7 @@ import { } from "@uploadthing/shared"; import type { ContentDisposition, UploadThingError } from "@uploadthing/shared"; -import type { MPUResponse } from "./types"; +import type { MPUResponse } from "./shared-schemas"; import type { UTReporter } from "./ut-reporter"; export const uploadMultipartWithProgress = ( @@ -47,7 +47,7 @@ export const uploadMultipartWithProgress = ( Effect.retry({ while: (error) => error instanceof RetryError, times: isTest ? 3 : 10, // less retries in tests just to make it faster - schedule: exponentialBackoff, + schedule: exponentialBackoff(), }), ); }, diff --git a/packages/uploadthing/src/internal/multi-part.server.ts b/packages/uploadthing/src/internal/multi-part.server.ts index a647d11090..1c154510e2 100644 --- a/packages/uploadthing/src/internal/multi-part.server.ts +++ b/packages/uploadthing/src/internal/multi-part.server.ts @@ -13,8 +13,8 @@ import { import type { ContentDisposition } from "@uploadthing/shared"; import type { FileEsque } from "../sdk/types"; -import { FailureCallbackResponseSchema } from "./shared-schemas"; -import type { MPUResponse } from "./types"; +import type { MPUResponse } from "./shared-schemas"; +import { FailureCallbackResponse } from "./shared-schemas"; export const uploadMultipart = (file: FileEsque, presigned: MPUResponse) => Effect.gen(function* () { @@ -84,7 +84,7 @@ const uploadPart = (opts: { ), Effect.retry({ while: (res) => res instanceof RetryError, - schedule: exponentialBackoff, + schedule: exponentialBackoff(), times: opts.maxRetries, }), Effect.tapErrorTag("RetryError", () => @@ -144,7 +144,7 @@ export const abortMultipartUpload = (presigned: { }, ).pipe( Effect.andThen(parseResponseJson), - Effect.andThen(S.decodeUnknown(FailureCallbackResponseSchema)), + Effect.andThen(S.decodeUnknown(FailureCallbackResponse)), Effect.withSpan("abortMultipartUpload", { attributes: { presigned }, }), diff --git a/packages/uploadthing/src/internal/presigned-post.browser.ts b/packages/uploadthing/src/internal/presigned-post.browser.ts index f964946268..606d8de1d9 100644 --- a/packages/uploadthing/src/internal/presigned-post.browser.ts +++ b/packages/uploadthing/src/internal/presigned-post.browser.ts @@ -2,7 +2,8 @@ import * as Effect from "effect/Effect"; import type { FetchContext, UploadThingError } from "@uploadthing/shared"; -import type { PSPResponse, UTEvents } from "./types"; +import type { PSPResponse } from "./shared-schemas"; +import type { UTEvents } from "./types"; import type { UTReporter } from "./ut-reporter"; export const uploadPresignedPostWithProgress = ( diff --git a/packages/uploadthing/src/internal/presigned-post.server.ts b/packages/uploadthing/src/internal/presigned-post.server.ts index fe27a8d32c..90efebcd7b 100644 --- a/packages/uploadthing/src/internal/presigned-post.server.ts +++ b/packages/uploadthing/src/internal/presigned-post.server.ts @@ -9,8 +9,8 @@ import { } from "@uploadthing/shared"; import type { FileEsque } from "../sdk/types"; -import { FailureCallbackResponseSchema } from "./shared-schemas"; -import type { PSPResponse } from "./types"; +import type { PSPResponse } from "./shared-schemas"; +import { FailureCallbackResponse } from "./shared-schemas"; export const uploadPresignedPost = (file: FileEsque, presigned: PSPResponse) => Effect.gen(function* () { @@ -38,7 +38,7 @@ export const uploadPresignedPost = (file: FileEsque, presigned: PSPResponse) => headers: { "Content-Type": "application/json" }, }).pipe( Effect.andThen(parseResponseJson), - Effect.andThen(S.decodeUnknown(FailureCallbackResponseSchema)), + Effect.andThen(S.decodeUnknown(FailureCallbackResponse)), ), ), ); diff --git a/packages/uploadthing/src/internal/shared-schemas.ts b/packages/uploadthing/src/internal/shared-schemas.ts index 6d95af343f..31b0a9d11a 100644 --- a/packages/uploadthing/src/internal/shared-schemas.ts +++ b/packages/uploadthing/src/internal/shared-schemas.ts @@ -12,40 +12,33 @@ export const ACLSchema = S.Literal(...ValidACLs); * ============================================================================= */ -export const FileUploadDataSchema = S.Struct({ - name: S.String, - size: S.Number, - type: S.String, -}); /** * Properties from the web File object, this is what the client sends when initiating an upload */ -export type FileUploadData = S.Schema.Type; - -export const FileUploadDataWithCustomIdSchema = S.extend( - FileUploadDataSchema, - S.Struct({ - customId: S.NullOr(S.String), - }), -); +export class FileUploadData extends S.Class("FileUploadData")({ + name: S.String, + size: S.Number, + type: S.String, +}) {} + /** * `.middleware()` can add a customId to the incoming file data */ -export type FileUploadDataWithCustomId = S.Schema.Type< - typeof FileUploadDataWithCustomIdSchema ->; - -export const UploadedFileDataSchema = S.extend( - FileUploadDataWithCustomIdSchema, - S.Struct({ - key: S.String, - url: S.String, - }), -); +export class FileUploadDataWithCustomId extends FileUploadData.extend( + "FileUploadDataWithCustomId", +)({ + customId: S.NullOr(S.String), +}) {} + /** * When files are uploaded, we get back a key and a URL for the file */ -export type UploadedFileData = S.Schema.Type; +export class UploadedFileData extends FileUploadDataWithCustomId.extend( + "UploadedFileData", +)({ + key: S.String, + url: S.String, +}) {} /** * When the client has uploaded a file and polled for data returned by `.onUploadComplete()` @@ -63,7 +56,9 @@ export interface ClientUploadedFileData extends UploadedFileData { * ============================================================================= */ -export const PresignedBaseSchema = S.Struct({ +export class PresignedBase extends S.Class( + "PresignedBaseSchema", +)({ key: S.String, fileName: S.String, fileType: S.String as S.Schema, @@ -72,31 +67,29 @@ export const PresignedBaseSchema = S.Struct({ pollingUrl: S.String, contentDisposition: ContentDispositionSchema, customId: S.NullOr(S.String), -}); - -export const MPUResponseSchema = S.extend( - PresignedBaseSchema, - S.Struct({ - urls: S.Array(S.String), - uploadId: S.String, - chunkSize: S.Number, - chunkCount: S.Number, - }), -); - -export const PSPResponseSchema = S.extend( - PresignedBaseSchema, - S.Struct({ - url: S.String, - fields: S.Record(S.String, S.String), - }), -); - -export const PresignedURLResponseSchema = S.Array( - S.Union(PSPResponseSchema, MPUResponseSchema), -); - -export const PollUploadResponseSchema = S.Struct({ +}) {} + +export class MPUResponse extends PresignedBase.extend( + "MPUResponseSchema", +)({ + urls: S.Array(S.String), + uploadId: S.String, + chunkSize: S.Number, + chunkCount: S.Number, +}) {} + +export class PSPResponse extends PresignedBase.extend( + "PSPResponseSchema", +)({ + url: S.String, + fields: S.Record(S.String, S.String), +}) {} + +export const PresignedURLResponse = S.Array(S.Union(PSPResponse, MPUResponse)); + +export class PollUploadResponse extends S.Class( + "PollUploadResponse", +)({ status: S.String, fileData: S.optional( S.Struct({ @@ -111,16 +104,20 @@ export const PollUploadResponseSchema = S.Struct({ callbackSlug: S.optional(S.String), }), ), -}); +}) {} -export const FailureCallbackResponseSchema = S.Struct({ +export class FailureCallbackResponse extends S.Class( + "FailureCallbackResponse", +)({ success: S.Boolean, message: S.optional(S.String), -}); +}) {} -export const ServerCallbackPostResponseSchema = S.Struct({ +export class ServerCallbackPostResponse extends S.Class( + "ServerCallbackPostResponse", +)({ status: S.String, -}); +}) {} /** * ============================================================================= @@ -128,19 +125,25 @@ export const ServerCallbackPostResponseSchema = S.Struct({ * ============================================================================= */ -export const UploadActionPayload = S.Struct({ - files: S.Array(FileUploadDataSchema), +export class UploadActionPayload extends S.Class( + "UploadActionPayload", +)({ + files: S.Array(FileUploadData), input: S.Unknown as S.Schema, -}); +}) {} -export const FailureActionPayload = S.Struct({ +export class FailureActionPayload extends S.Class( + "FailureActionPayload", +)({ fileKey: S.String, uploadId: S.NullOr(S.String), storageProviderError: S.optional(S.String), fileName: S.String, -}); +}) {} -export const MultipartCompleteActionPayload = S.Struct({ +export class MultipartCompleteActionPayload extends S.Class( + "MultipartCompleteActionPayload", +)({ fileKey: S.String, uploadId: S.String, etags: S.Array( @@ -149,4 +152,4 @@ export const MultipartCompleteActionPayload = S.Struct({ partNumber: S.Number, }), ), -}); +}) {} diff --git a/packages/uploadthing/src/internal/to-web-request.ts b/packages/uploadthing/src/internal/to-web-request.ts index e00c92e0d1..e365ac423f 100644 --- a/packages/uploadthing/src/internal/to-web-request.ts +++ b/packages/uploadthing/src/internal/to-web-request.ts @@ -1,4 +1,4 @@ -import { TaggedError } from "effect/Data"; +import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import { process } from "std-env"; @@ -11,9 +11,11 @@ type IncomingMessageLike = { body?: any; }; -class InvalidURL extends TaggedError("InvalidURL")<{ +class InvalidURL extends Data.Error<{ reason: string; }> { + readonly _tag = "InvalidURL"; + readonly name = "InvalidURLError"; constructor(attemptedUrl: string, base?: string) { Effect.runSync( Effect.logError( diff --git a/packages/uploadthing/src/internal/types.ts b/packages/uploadthing/src/internal/types.ts index 42a8edb9da..d0f9c0bccc 100644 --- a/packages/uploadthing/src/internal/types.ts +++ b/packages/uploadthing/src/internal/types.ts @@ -18,21 +18,15 @@ import type { LogLevel } from "./logger"; import type { JsonParser } from "./parser"; import type { FailureActionPayload, - MPUResponseSchema, MultipartCompleteActionPayload, - PresignedBaseSchema, - PresignedURLResponseSchema, - PSPResponseSchema, + PresignedURLResponse, UploadActionPayload, } from "./shared-schemas"; /** * Returned by `/api/prepareUpload` and `/api/uploadFiles` */ -export type PresignedBase = S.Schema.Type; -export type PSPResponse = S.Schema.Type; -export type MPUResponse = S.Schema.Type; -export type PresignedURLs = S.Schema.Type; +export type PresignedURLs = S.Schema.Type; /** * Marker used to append a `customId` to the incoming file data in `.middleware()` @@ -261,7 +255,7 @@ export const isUploadThingHook = (input: unknown): input is UploadThingHook => export type UTEvents = { upload: { in: S.Schema.Type; - out: S.Schema.Type; + out: S.Schema.Type; }; failure: { in: S.Schema.Type; diff --git a/packages/uploadthing/src/internal/validate-request-input.ts b/packages/uploadthing/src/internal/validate-request-input.ts index 2508174eae..1ee2d00756 100644 --- a/packages/uploadthing/src/internal/validate-request-input.ts +++ b/packages/uploadthing/src/internal/validate-request-input.ts @@ -1,6 +1,6 @@ import type * as S from "@effect/schema/Schema"; import * as Context from "effect/Context"; -import { TaggedError } from "effect/Data"; +import * as Data from "effect/Data"; import * as Effect from "effect/Effect"; import { isDevelopment } from "std-env"; @@ -43,18 +43,22 @@ import { VALID_UT_HOOKS, } from "./types"; -class FileSizeMismatch extends TaggedError("FileSizeMismatch")<{ +class FileSizeMismatch extends Data.Error<{ reason: string; }> { + readonly _tag = "FileSizeMismatch"; + readonly name = "FileSizeMismatchError"; constructor(type: FileRouterInputKey, max: FileSize, actual: number) { const reason = `You uploaded a ${type} file that was ${bytesToFileSize(actual)}, but the limit for that type is ${max}`; super({ reason }); } } -class FileCountMismatch extends TaggedError("FileCountMismatch")<{ +class FileCountMismatch extends Data.Error<{ reason: string; }> { + readonly _tag = "FileCountMismatch"; + readonly name = "FileCountMismatchError"; constructor( type: FileRouterInputKey, boundtype: "minimum" | "maximum", @@ -149,10 +153,11 @@ type RequestInputService = RequestInputBase & | { hook: UploadThingHook; action: null } ); -export class RequestInput extends Context.Tag("uploadthing/RequestInput")< - RequestInput, - RequestInputService ->() {} +export class RequestInput + extends /** #__PURE__ */ Context.Tag("uploadthing/RequestInput")< + RequestInput, + RequestInputService + >() {} export const parseAndValidateRequest = ( input: RequestHandlerInput>, diff --git a/packages/uploadthing/src/sdk/utils.ts b/packages/uploadthing/src/sdk/utils.ts index ab38767d2a..3be02dafd1 100644 --- a/packages/uploadthing/src/sdk/utils.ts +++ b/packages/uploadthing/src/sdk/utils.ts @@ -23,8 +23,8 @@ import type { import { uploadMultipart } from "../internal/multi-part.server"; import { uploadPresignedPost } from "../internal/presigned-post.server"; import { - PollUploadResponseSchema, - PresignedURLResponseSchema, + PollUploadResponse, + PresignedURLResponse, } from "../internal/shared-schemas"; import type { UploadedFileData } from "../types"; import type { FileEsque, UrlWithOverrides } from "./types"; @@ -144,7 +144,7 @@ const getPresignedUrls = (input: UploadFilesInternalOptions) => yield* Effect.logDebug("Getting presigned URLs for files", fileData); const responseSchema = S.Struct({ - data: PresignedURLResponseSchema, + data: PresignedURLResponse, }); const presigneds = yield* fetchEff( @@ -190,7 +190,7 @@ const uploadFile = ( generateUploadThingURL(`/api/pollUpload/${presigned.key}`), ).pipe( Effect.andThen(parseResponseJson), - Effect.andThen(S.decodeUnknown(PollUploadResponseSchema)), + Effect.andThen(S.decodeUnknown(PollUploadResponse)), Effect.tap(Effect.logDebug("Polled upload", presigned.key)), Effect.andThen((res) => res.status === "done" @@ -199,7 +199,7 @@ const uploadFile = ( ), Effect.retry({ while: (err) => err instanceof RetryError, - schedule: exponentialBackoff, + schedule: exponentialBackoff(), }), Effect.catchTag("RetryError", (e) => Effect.die(e)), ); diff --git a/packages/uploadthing/test/__test-helpers.ts b/packages/uploadthing/test/__test-helpers.ts index 7cfc351989..9ec070ce09 100644 --- a/packages/uploadthing/test/__test-helpers.ts +++ b/packages/uploadthing/test/__test-helpers.ts @@ -10,11 +10,11 @@ import { generateUploadThingURL } from "@uploadthing/shared"; import { UPLOADTHING_VERSION } from "../src/internal/constants"; import type { - ActionType, MPUResponse, PresignedBase, PSPResponse, -} from "../src/internal/types"; +} from "../src/internal/shared-schemas"; +import type { ActionType } from "../src/internal/types"; export const requestSpy = vi.fn<[string, RequestInit]>(); export const requestsToDomain = (domain: string) => diff --git a/packages/uploadthing/test/request-handler.test.ts b/packages/uploadthing/test/request-handler.test.ts index 15ed1e140a..ee5efa7d81 100644 --- a/packages/uploadthing/test/request-handler.test.ts +++ b/packages/uploadthing/test/request-handler.test.ts @@ -3,8 +3,8 @@ import { z } from "zod"; import { signPayload } from "@uploadthing/shared"; +import { UploadedFileData } from "../src/internal/shared-schemas"; import { createRouteHandler, createUploadthing } from "../src/server"; -import type { UploadedFileData } from "../src/types"; import { baseHeaders, createApiUrl, @@ -330,14 +330,14 @@ describe(".onUploadComplete()", () => { const payload = JSON.stringify({ status: "uploaded", metadata: {}, - file: { + file: new UploadedFileData({ url: "https://utfs.io/f/some-random-key.png", name: "foo.png", key: "some-random-key.png", size: 48, type: "image/png", customId: null, - } satisfies UploadedFileData, + }), }); const signature = await signPayload(payload, "sk_live_test123"); @@ -372,14 +372,14 @@ describe(".onUploadComplete()", () => { const payload = JSON.stringify({ status: "uploaded", metadata: {}, - file: { + file: new UploadedFileData({ url: "https://utfs.io/f/some-random-key.png", name: "foo.png", key: "some-random-key.png", size: 48, type: "image/png", customId: null, - } satisfies UploadedFileData, + }), }); const res = await handlers.POST( @@ -403,14 +403,14 @@ describe(".onUploadComplete()", () => { const payload = JSON.stringify({ status: "uploaded", metadata: {}, - file: { + file: new UploadedFileData({ url: "https://utfs.io/f/some-random-key.png", name: "foo.png", key: "some-random-key.png", size: 48, type: "image/png", customId: null, - } satisfies UploadedFileData, + }), }); const signature = await signPayload(payload, "sk_live_badkey"); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 98ec83d302..a1e03a18d9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -695,7 +695,7 @@ importers: specifier: ^0.5.10 version: 0.5.13(tailwindcss@3.4.3) '@uploadthing/react': - specifier: ^6.5.3 + specifier: ^6.5.4 version: link:../../packages/react class-variance-authority: specifier: ^0.7.0 @@ -728,7 +728,7 @@ importers: specifier: ^2.2.1 version: 2.3.0 uploadthing: - specifier: 6.10.3 + specifier: 6.10.4 version: link:../../packages/uploadthing use-debounce: specifier: ^9.0.3 @@ -1340,7 +1340,7 @@ importers: version: link:../tsconfig tsup: specifier: 8.0.2 - version: 8.0.2(typescript@5.4.5) + version: 8.0.2(postcss@8.4.38)(typescript@5.4.5) typescript: specifier: ^5.4.5 version: 5.4.5 @@ -6577,7 +6577,7 @@ packages: deepmerge: 4.3.1 is-builtin-module: 3.2.1 is-module: 1.0.0 - resolve: 1.22.8 + resolve: 1.22.4 rollup: 3.29.4 dev: true @@ -18844,22 +18844,6 @@ packages: yaml: 1.10.2 dev: false - /postcss-load-config@4.0.1: - resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - dependencies: - lilconfig: 2.1.0 - yaml: 2.4.1 - dev: true - /postcss-load-config@4.0.1(postcss@8.4.38): resolution: {integrity: sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==} engines: {node: '>= 14'} @@ -22267,45 +22251,6 @@ packages: - ts-node dev: true - /tsup@8.0.2(typescript@5.4.5): - resolution: {integrity: sha512-NY8xtQXdH7hDUAZwcQdY/Vzlw9johQsaqf7iwZ6g1DOUlFYQ5/AtVAjTvihhEyeRlGo4dLRVHtrRaL35M1daqQ==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - '@microsoft/api-extractor': ^7.36.0 - '@swc/core': ^1 - postcss: ^8.4.12 - typescript: '>=4.5.0' - peerDependenciesMeta: - '@microsoft/api-extractor': - optional: true - '@swc/core': - optional: true - postcss: - optional: true - typescript: - optional: true - dependencies: - bundle-require: 4.0.1(esbuild@0.19.2) - cac: 6.7.14 - chokidar: 3.5.3 - debug: 4.3.4 - esbuild: 0.19.2 - execa: 5.1.1 - globby: 11.1.0 - joycon: 3.1.1 - postcss-load-config: 4.0.1 - resolve-from: 5.0.0 - rollup: 4.16.2 - source-map: 0.8.0-beta.0 - sucrase: 3.34.0 - tree-kill: 1.2.2 - typescript: 5.4.5 - transitivePeerDependencies: - - supports-color - - ts-node - dev: true - /tsx@4.7.2: resolution: {integrity: sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==} engines: {node: '>=18.0.0'}