Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slim down API, focus on happy path #1

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
317 changes: 24 additions & 293 deletions README.md

Large diffs are not rendered by default.

60 changes: 13 additions & 47 deletions example/convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
*/

import type * as example from "../example.js";
import type * as http from "../http.js";

import type {
ApiFromModules,
Expand All @@ -26,7 +25,6 @@ import type {
*/
declare const fullApi: ApiFromModules<{
example: typeof example;
http: typeof http;
}>;
declare const fullApiWithMounts: typeof fullApi;

Expand All @@ -42,46 +40,13 @@ export declare const internal: FilterApi<
export declare const components: {
r2: {
lib: {
deleteObject: FunctionReference<
"action",
deleteMetadata: FunctionReference<
"mutation",
"internal",
{
accessKeyId: string;
bucket: string;
endpoint: string;
key: string;
secretAccessKey: string;
},
{ key: string },
any
>;
exportConvexFilesToR2: FunctionReference<
"action",
"internal",
{
accessKeyId: string;
batchSize?: number;
bucket: string;
deleteFn: string;
endpoint: string;
listFn: string;
nextFn: string;
secretAccessKey: string;
uploadFn: string;
},
any
>;
generateUploadUrl: FunctionReference<
"action",
"internal",
{
accessKeyId: string;
bucket: string;
endpoint: string;
secretAccessKey: string;
},
any
>;
getMetadata: FunctionReference<
deleteObject: FunctionReference<
"action",
"internal",
{
Expand All @@ -93,29 +58,30 @@ export declare const components: {
},
any
>;
getUrl: FunctionReference<
"query",
getMetadata: FunctionReference<"query", "internal", { key: string }, any>;
insertMetadata: FunctionReference<
"mutation",
"internal",
{
accessKeyId: string;
bucket: string;
endpoint: string;
contentType: string;
key: string;
secretAccessKey: string;
sha256: string;
size: number;
},
any
>;
store: FunctionReference<
syncMetadata: FunctionReference<
"action",
"internal",
{
accessKeyId: string;
bucket: string;
endpoint: string;
key: string;
secretAccessKey: string;
url: string;
},
any
null
>;
};
};
Expand Down
34 changes: 1 addition & 33 deletions example/convex/example.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,7 @@ import { components, internal } from "./_generated/api";
import { R2 } from "@convex-dev/r2";
const r2 = new R2(components.r2);

export const listConvexFiles = r2.listConvexFiles();
export const uploadFile = r2.uploadFile();
export const deleteFile = r2.deleteFile();

export const exportConvexFilesToR2 = internalAction({
handler: async (ctx) => {
await r2.exportConvexFilesToR2(ctx, {
listFn: internal.example.listConvexFiles,
uploadFn: internal.example.uploadFile,
nextFn: internal.example.exportConvexFilesToR2,
deleteFn: internal.example.deleteFile,
batchSize: 10,
});
},
});

export const generateUploadUrl = action(() => {
return r2.generateUploadUrl();
});
export const { generateUploadUrl, syncMetadata } = r2.api();

export const getRecentImages = query({
args: {},
Expand All @@ -50,20 +32,6 @@ export const sendImage = mutation({
},
});

export const httpSendImage = internalMutation({
args: { key: v.string(), requestUrl: v.string() },
handler: async (ctx, args) => {
const author = new URL(args.requestUrl).searchParams.get("author");
if (!author) {
throw new Error("Author is required");
}
await ctx.db.insert("images", {
key: args.key,
author,
});
},
});

export const deleteImageRef = internalMutation({
args: { key: v.string() },
handler: async (ctx, args) => {
Expand Down
13 changes: 0 additions & 13 deletions example/convex/http.ts

This file was deleted.

74 changes: 6 additions & 68 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import "./App.css";
import { useAction, useMutation, useQuery } from "convex/react";
import { FormEvent, useRef, useState } from "react";
import { api } from "../convex/_generated/api";

// Set to true to use HTTP Action instead of signed URL
const GET_VIA_HTTP = true;
const SEND_VIA_HTTP = true;
import { useUploadFile } from "@convex-dev/r2/react";

export function App() {
const generateUploadUrl = useAction(api.example.generateUploadUrl);
const uploadFile = useUploadFile(api.example);
const sendImage = useMutation(api.example.sendImage);
const deleteImage = useAction(api.example.deleteImage);
const images = useQuery(api.example.getRecentImages);
Expand All @@ -18,72 +15,21 @@ export function App() {

const [name] = useState(() => "User " + Math.floor(Math.random() * 10000));

async function handleSendImageViaSignedUrl(event: FormEvent) {
async function handleSendImage(event: FormEvent) {
event.preventDefault();
setSending(true);
// Step 1: Get a short-lived upload URL
const { url, key } = await generateUploadUrl();
// Step 2: PUT the file to the URL
try {
const result = await fetch(url, {
method: "PUT",
headers: { "Content-Type": selectedImage!.type },
body: selectedImage,
});
if (!result.ok) {
setSending(false);
throw new Error(`Failed to upload image: ${result.statusText}`);
}
} catch (error) {
setSending(false);
throw new Error(`Failed to upload image: ${error}`);
}
// Step 3: Save the newly allocated storage id to the database
const key = await uploadFile(selectedImage!);
await sendImage({ key, author: name });
setSending(false);
setSelectedImage(null);
imageInput.current!.value = "";
}

async function handleSendImageViaHttp(event: FormEvent) {
event.preventDefault();
setSending(true);

const sendImageUrl = new URL(
// Use Convex Action URL
"https://giant-kangaroo-636.convex.site/r2/send"
);
sendImageUrl.searchParams.set("author", name);

try {
const result = await fetch(sendImageUrl, {
method: "POST",
headers: { "Content-Type": selectedImage!.type },
body: selectedImage,
});
if (!result.ok) {
setSending(false);
throw new Error(`Failed to upload image: ${result.statusText}`);
}
} catch (error) {
setSending(false);
throw new Error(`Failed to upload image: ${error}`);
}

setSending(false);
setSelectedImage(null);
imageInput.current!.value = "";
}

return (
<>
<h1>Convex R2 Component Example</h1>
<div className="card">
<form
onSubmit={
SEND_VIA_HTTP ? handleSendImageViaHttp : handleSendImageViaSignedUrl
}
>
<form onSubmit={handleSendImage}>
<input
type="file"
accept="image/*"
Expand All @@ -101,15 +47,7 @@ export function App() {
{images?.map((image) => (
<div key={image._id} className="image-row">
<p>{image.author}</p>
<img
src={
GET_VIA_HTTP
? `https://giant-kangaroo-636.convex.site/r2/get/${image.key}`
: image.url
}
alt={image.author}
width={80}
/>
<img src={image.url} alt={image.author} width={80} />
<button onClick={() => deleteImage({ key: image.key })}>
Delete
</button>
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,13 @@
}
},
"peerDependencies": {
"convex": "~1.16.5 || ~1.17.0"
"convex": "~1.16.5 || ~1.17.0",
"react": "^18.3.1"
},
"devDependencies": {
"@eslint/js": "^9.9.1",
"@types/node": "^18.17.0",
"@types/react": "^18.3.3",
"convex-test": "^0.0.33",
"eslint": "^9.9.1",
"globals": "^15.9.0",
Expand Down
Loading