Skip to content

Commit

Permalink
fix: make @uploadthing/shared treeshakeable (#808)
Browse files Browse the repository at this point in the history
  • Loading branch information
juliusmarminge authored May 16, 2024
1 parent fa2fc7e commit 4fea8f4
Show file tree
Hide file tree
Showing 20 changed files with 188 additions and 208 deletions.
8 changes: 8 additions & 0 deletions .changeset/beige-bats-press.md
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions examples/with-novel/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
22 changes: 12 additions & 10 deletions packages/shared/src/effect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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))),
);
7 changes: 5 additions & 2 deletions packages/shared/src/error.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TaggedError } from "effect/Data";
import * as Data from "effect/Data";

import type { Json } from "./types";
import { isObject } from "./utils";
Expand Down Expand Up @@ -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;
Expand Down
52 changes: 35 additions & 17 deletions packages/shared/src/tagged-errors.ts
Original file line number Diff line number Diff line change
@@ -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.`
Expand All @@ -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
Expand All @@ -60,30 +72,36 @@ 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;
body: unknown;
headers: Record<string, string>;
};
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<T = unknown> extends TaggedError(
"BadRequestError",
)<{
export class BadRequestError<T = unknown> 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;
Expand Down
2 changes: 1 addition & 1 deletion packages/uploadthing/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
11 changes: 5 additions & 6 deletions packages/uploadthing/src/internal/dev-hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,15 +28,15 @@ 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)
: Effect.fail(new RetryError()),
),
Effect.retry({
while: (err) => err instanceof RetryError,
schedule: exponentialBackoff,
schedule: exponentialBackoff(),
}),
Effect.catchTag("RetryError", (e) => Effect.die(e)),
);
Expand All @@ -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({
Expand Down
12 changes: 6 additions & 6 deletions packages/uploadthing/src/internal/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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),
}),
),
Expand Down Expand Up @@ -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 };
});
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions packages/uploadthing/src/internal/multi-part.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down Expand Up @@ -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(),
}),
);
},
Expand Down
8 changes: 4 additions & 4 deletions packages/uploadthing/src/internal/multi-part.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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* () {
Expand Down Expand Up @@ -84,7 +84,7 @@ const uploadPart = (opts: {
),
Effect.retry({
while: (res) => res instanceof RetryError,
schedule: exponentialBackoff,
schedule: exponentialBackoff(),
times: opts.maxRetries,
}),
Effect.tapErrorTag("RetryError", () =>
Expand Down Expand Up @@ -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 },
}),
Expand Down
3 changes: 2 additions & 1 deletion packages/uploadthing/src/internal/presigned-post.browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
6 changes: 3 additions & 3 deletions packages/uploadthing/src/internal/presigned-post.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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* () {
Expand Down Expand Up @@ -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)),
),
),
);
Expand Down
Loading

0 comments on commit 4fea8f4

Please sign in to comment.