Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/backend-adapters/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"dev:effect": "NODE_ENV=development PORT=3003 tsx watch src/effect-platform.ts"
},
"dependencies": {
"convex": "^1.16.0",
"@effect/platform": "0.68.5",
"@effect/platform-node": "0.63.5",
"@effect/schema": "0.75.4",
Expand Down
28 changes: 28 additions & 0 deletions examples/backend-adapters/server/src/convex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// In a real Convex app, you'd create this file at `convex/http.ts` to set
// up HTTP routes for UploadThing.

import { httpRouter } from "convex/server";
import { addUploadthingRoutes, createUploadthing, FileRouter } from "uploadthing/convex"

// In a real app, you can wire up `cereateUploadthing` to your Convex
// schema for increased type safety.
// ```ts
// import schema from "./schema";
// const f = createUploadthing({ schema })
// ```
const f = createUploadthing();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It may be better to have a separate example showing the "real" way as you suggested in the PR comment. without it, this example doesnt really hold much value IMO

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds good, I'll remove this and port it to a full example.


const router = {
imageUploader: f({ image: { maxFileSize: "4MB" } })
.middleware(async ({ event }) => {
const identity = await event.auth.getUserIdentity();
return { userId: identity?.subject ?? "nothing" };
})
.onUploadComplete((args) => {
return { uploadedBy: args.metadata.userId };
}),
} satisfies FileRouter;

const http = httpRouter();
addUploadthingRoutes(http, { router });
export default http;
15 changes: 15 additions & 0 deletions packages/uploadthing/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,16 @@
"default": "./h3/index.cjs"
}
},
"./convex": {
"import": {
"types": "./convex/index.d.ts",
"default": "./convex/index.js"
},
"require": {
"types": "./convex/index.d.cts",
"default": "./convex/index.cjs"
}
},
"./remix": {
"import": {
"types": "./remix/index.d.ts",
Expand Down Expand Up @@ -126,6 +136,7 @@
},
"files": [
"client",
"convex",
"effect-platform",
"express",
"fastify",
Expand Down Expand Up @@ -182,6 +193,7 @@
"zod": "^3.23.8"
},
"peerDependencies": {
"convex": "*",
"express": "*",
"fastify": "*",
"h3": "*",
Expand All @@ -198,6 +210,9 @@
"fastify": {
"optional": true
},
"convex": {
"optional": true
},
"h3": {
"optional": true
},
Expand Down
94 changes: 94 additions & 0 deletions packages/uploadthing/src/convex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import type {
DataModelFromSchemaDefinition,
GenericActionCtx,
GenericDataModel,
HttpRouter,
SchemaDefinition,
} from "convex/server";
import { httpActionGeneric, httpRouter } from "convex/server";
import * as Effect from "effect/Effect";

import type { Json } from "@uploadthing/shared";

import { makeAdapterHandler } from "./internal/handler";
import type { FileRouter, RouteHandlerOptions } from "./internal/types";
import { createBuilder } from "./internal/upload-builder";
import type { CreateBuilderOptions } from "./internal/upload-builder";

export type { FileRouter };

type MiddlewareArgs<DataModel extends GenericDataModel> = {
req: Request;
res: undefined;
event: GenericActionCtx<DataModel>;
};

type ConvexBuilderOptions<
TErrorShape extends Json,
SchemaDef extends SchemaDefinition<any, boolean>,
> = CreateBuilderOptions<TErrorShape> & {
schema?: SchemaDef;
};

export const createUploadthing = <
TErrorShape extends Json,
SchemaDef extends SchemaDefinition<any, boolean>,
>(
opts?: ConvexBuilderOptions<TErrorShape, SchemaDef>,
) =>
createBuilder<
MiddlewareArgs<DataModelFromSchemaDefinition<SchemaDef>>,
TErrorShape
>(opts);

export const addUploadthingRoutes = <TRouter extends FileRouter>(
router: HttpRouter,
opts: RouteHandlerOptions<TRouter>,
) => {
const handler = makeAdapterHandler<
[GenericActionCtx<GenericDataModel>, Request]
>(
(ctx, req) => Effect.succeed({ req, res: undefined, event: ctx }),
(_, req) => Effect.succeed(req),
opts,
"convex",
);

router.route({
method: "GET",
path: "/api/uploadthing",
handler: httpActionGeneric(handler),
});

router.route({
method: "POST",
path: "/api/uploadthing",
handler: httpActionGeneric(handler),
});

router.route({
method: "OPTIONS",
path: "/api/uploadthing",
handler: httpActionGeneric((_ctx, { headers }) => {
const isCorsRequest =
headers.get("Origin") != null &&
headers.get("Access-Control-Request-Method") != null &&
headers.get("Access-Control-Request-Headers") != null;

if (!isCorsRequest) {
return Promise.resolve(new Response());
}
return Promise.resolve(
new Response(null, {
status: 200,
headers: {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS",
"Access-Control-Allow-Headers": "*",
"Access-Control-Max-Age": "86400",
},
}),
);
}),
});
};
1 change: 1 addition & 0 deletions packages/uploadthing/turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"build": {
"outputs": [
"client/**",
"convex/**",
"effect-platform/**",
"express/**",
"fastify/**",
Expand Down
Loading