Skip to content
Merged
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
23 changes: 0 additions & 23 deletions .github/workflows/node.js.yml

This file was deleted.

38 changes: 38 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test and lint
concurrency:
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

on:
push:
branches: [main]
pull_request:
branches: ["**"]

jobs:
check:
name: Test and lint
runs-on: ubuntu-latest
timeout-minutes: 30

steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5

- name: Node setup
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5
with:
cache-dependency-path: package.json
node-version: "20.x"
cache: "npm"

- name: Install and build
run: |
npm i
npm run build
- name: Publish package for testing branch
run: npx pkg-pr-new publish || echo "Have you set up pkg-pr-new for this repo?"
- name: Test
run: |
npm run test
npm run typecheck
npm run lint
5 changes: 1 addition & 4 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ dist-ssr
explorations
node_modules
.eslintcache
# components are libraries!
package-lock.json

# this is a package-json-redirect stub dir, see https://github.com/andrewbranch/example-subpath-exports-ts-compat?tab=readme-ov-file
react/package.json
# npm pack output
*.tgz
*.tsbuildinfo
3 changes: 2 additions & 1 deletion .prettierrc.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"trailingComma": "es5"
"trailingComma": "all",
"proseWrap": "always"
}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Changelog

## 0.8.0

- Adds /test and /\_generated/component.js entrypoints
- Drops commonjs support
- Improves source mapping for generated files
- Changes to a statically generated component API
28 changes: 9 additions & 19 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,37 @@

```sh
npm i
cd example
npm i
npx convex dev
npm run dev
```

## Testing

```sh
rm -rf dist/ && npm run build
npm run clean
npm run build
npm run typecheck
npm run test
cd example
npm run lint
cd ..
npm run test
```

## Deploying

### Building a one-off package

```sh
rm -rf dist/ && npm run build
npm run clean
npm ci
npm pack
```

### Deploying a new version

```sh
# this will change the version and commit it (if you run it in the root directory)
npm version patch
npm publish --dry-run
# sanity check files being included
npm publish
git push --tags
npm run release
```

#### Alpha release

The same as above, but it requires extra flags so the release is only installed with `@alpha`:
or for alpha release:

```sh
npm version prerelease --preid alpha
npm publish --tag alpha
npm run alpha
```
67 changes: 39 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,10 @@ const response = await fetch(url);
- [Create a Cloudflare account](https://cloudflare.com)
- [Create an R2 bucket](https://developers.cloudflare.com/r2/buckets/create-buckets/)
- Set the bucket name as an environment variable `R2_BUCKET` in your Convex
deployment
- [Add a CORS policy](https://developers.cloudflare.com/r2/buckets/cors/#add-cors-policies-from-the-dashboard) to the bucket allowing GET and PUT requests from your
Convex app. You can also use '\*' to allow all origins (use with caution).
deployment via `npx convex env set R2_BUCKET <bucket-name>`.
- [Add a CORS policy](https://developers.cloudflare.com/r2/buckets/cors/#add-cors-policies-from-the-dashboard)
to the bucket allowing GET and PUT requests from your Convex app. You can also
use '\*' to allow all origins (use with caution).
```json
[
{
Expand All @@ -58,7 +59,8 @@ const response = await fetch(url);

### Convex App

You'll need a Convex App to use the component. Follow any of the [Convex quickstarts](https://docs.convex.dev/home) to set one up.
You'll need a Convex App to use the component. Follow any of the
[Convex quickstarts](https://docs.convex.dev/home) to set one up.

## Installation

Expand All @@ -68,12 +70,13 @@ Install the component package:
npm install @convex-dev/r2
```

Create a `convex.config.ts` file in your app's `convex/` folder and install the component by calling `use`:
Create a `convex.config.ts` file in your app's `convex/` folder and install the
component by calling `use`:

```ts
// convex/convex.config.ts
import { defineApp } from "convex/server";
import r2 from "@convex-dev/r2/convex.config";
import r2 from "@convex-dev/r2/convex.config.js";

const app = defineApp();
app.use(r2);
Expand All @@ -92,12 +95,13 @@ npx convex env set R2_BUCKET xxxxx
```

Note: All of these values can also be supplied as the second argument to `R2`.
This enables storing values in multiple buckets using the same component.
It also enables dynamically setting the values at runtime.
This enables storing values in multiple buckets using the same component. It
also enables dynamically setting the values at runtime.

## Uploading files

File uploads to R2 typically use signed urls. The R2 component provides hooks for React and Svelte that handle the entire upload process:
File uploads to R2 typically use signed urls. The R2 component provides hooks
for React and Svelte that handle the entire upload process:

- generates the signed url
- uploads the file to R2
Expand All @@ -117,12 +121,12 @@ File uploads to R2 typically use signed urls. The R2 component provides hooks fo
// const user = await userFromAuth(ctx);
// ...validate that the user can upload to this bucket
},
onUpload: async (ctx, bucket, key) => {
// ...do something with the key
// This technically runs in the `syncMetadata` mutation, as the upload
// is performed from the client side. Will run if using the `useUploadFile`
// hook, or if `syncMetadata` function is called directly. Runs after the
// `checkUpload` callback.
onUpload: async (ctx, bucket, key) => {
// ...do something with the key
// This technically runs in the `syncMetadata` mutation, as the upload
// is performed from the client side. Will run if using the `useUploadFile`
// hook, or if `syncMetadata` function is called directly. Runs after the
// `checkUpload` callback.
},
});
```
Expand Down Expand Up @@ -210,11 +214,12 @@ File uploads to R2 typically use signed urls. The R2 component provides hooks fo
### Using a custom object key

The `r2.generateUploadUrl` function generates a uuid to use as the object key by
default, but a custom key can be provided if desired. Note: the `generateUploadUrl`
function returned by `r2.clientApi` does not accept a custom key, as that
function is a mutation to be called from the client side and you don't want your
client defining your object keys. Providing a custom key requires making your
own mutation that calls the `generateUploadUrl` method of the `r2` instance.
default, but a custom key can be provided if desired. Note: the
`generateUploadUrl` function returned by `r2.clientApi` does not accept a custom
key, as that function is a mutation to be called from the client side and you
don't want your client defining your object keys. Providing a custom key
requires making your own mutation that calls the `generateUploadUrl` method of
the `r2` instance.

```ts
// convex/example.ts
Expand All @@ -241,7 +246,9 @@ export const generateUploadUrlWithCustomKey = mutation({

## Storing Files from Actions

Files can be stored in R2 directly from actions using the `r2.store` method. This is useful when you need to store files that are generated or downloaded on the server side.
Files can be stored in R2 directly from actions using the `r2.store` method.
This is useful when you need to store files that are generated or downloaded on
the server side.

```ts
// convex/example.ts
Expand Down Expand Up @@ -280,11 +287,13 @@ The `store` method:

## Serving Files

Files stored in R2 can be served to your users by generating a URL pointing to a given file.
Files stored in R2 can be served to your users by generating a URL pointing to a
given file.

### Generating file URLs in queries

The simplest way to serve files is to return URLs along with other data required by your app from queries and mutations.
The simplest way to serve files is to return URLs along with other data required
by your app from queries and mutations.

A file URL can be generated from a object key by the `r2.getUrl` function of the
R2 component client.
Expand All @@ -311,9 +320,9 @@ export const list = query({
{
// Custom expiration time in seconds, default is 900 (15 minutes)
expiresIn: 60 * 60 * 24, // 1 day
}
},
),
}))
})),
);
},
});
Expand All @@ -330,7 +339,8 @@ function Image({ message }: { message: { url: string } }) {

## Deleting Files

Files stored in R2 can be deleted from actions or mutations via the `r2.deleteObject` function, which accepts an object key.
Files stored in R2 can be deleted from actions or mutations via the
`r2.deleteObject` function, which accepts an object key.

```ts
// convex/images.ts
Expand Down Expand Up @@ -390,7 +400,8 @@ The returned document has the following fields:

### Listing and paginating metadata

Metadata can be listed or paginated from actions via `r2.listMetadata` and `r2.pageMetadata`.
Metadata can be listed or paginated from actions via `r2.listMetadata` and
`r2.pageMetadata`.

```ts
// convex/example.ts
Expand Down Expand Up @@ -451,7 +462,7 @@ export const { generateUploadUrl, syncMetadata, onSyncMetadata } = r2.clientApi(
// log metadata of synced object
console.log("metadata", metadata);
},
}
},
);
```

Expand Down
8 changes: 0 additions & 8 deletions commonjs.json

This file was deleted.

7 changes: 7 additions & 0 deletions convex.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"$schema": "./node_modules/convex/schemas/convex.schema.json",
"functions": "example/convex",
"codegen": {
"legacyComponentApi": false
}
}
Loading
Loading