Skip to content

Commit

Permalink
Merge branch 'main' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
bombastictranz authored Jun 22, 2024
2 parents deab79d + fa38a54 commit 887b6f9
Show file tree
Hide file tree
Showing 12 changed files with 415 additions and 78 deletions.
23 changes: 23 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,26 @@ This will:
- Push the commit and tag to GitHub
- Publish the package to npm
- Create a GitHub release

## Vendored Dependencies

We have a few dependencies that have been bundled into the vendor directory rather than adding external npm dependencies.

These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information.

* [eventsource-parser/stream](https://bundlejs.com/?bundle&q=eventsource-parser%40latest%2Fstream&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%2C%22platform%22%3A%22neutral%22%7D%7D)
* [streams-text-encoding/text-decoder-stream](https://bundlejs.com/?q=%40stardazed%2Fstreams-text-encoding&treeshake=%5B%7B+TextDecoderStream+%7D%5D&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%7D%7D)

> [!NOTE]
> The vendored implementation of `TextDecoderStream` requires
> the following patch to be applied to the output of bundlejs.com:
>
> ```diff
> constructor(label, options) {
> - this[decDecoder] = new TextDecoder(label, options);
> - this[decTransform] = new TransformStream(new TextDecodeTransformer(this[decDecoder]));
> + const decoder = new TextDecoder(label || "utf-8", options || {});
> + this[decDecoder] = decoder;
> + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder));
> }
> ```
87 changes: 62 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ and everything else you can do with
## Installation

This library requires Node.js >= 18.

Install it from npm:

```bash
Expand All @@ -20,7 +22,7 @@ npm install replicate

## Usage

Create the client:
Import or require the package:

```js
// CommonJS (default or using .cjs extension)
Expand All @@ -30,9 +32,11 @@ const Replicate = require("replicate");
import Replicate from "replicate";
```

```
Instantiate the client:

```js
const replicate = new Replicate({
// get your token from https://replicate.com/account
// get your token from https://replicate.com/account/api-tokens
auth: "my api token", // defaults to process.env.REPLICATE_API_TOKEN
});
```
Expand Down Expand Up @@ -148,8 +152,53 @@ await replicate.predictions.create({
// => {"id": "xyz", "status": "successful", ... }
```

## Verifying webhooks

To prevent unauthorized requests, Replicate signs every webhook and its metadata with a unique key for each user or organization. You can use this signature to verify the webhook indeed comes from Replicate before you process it.

This client includes a `validateWebhook` convenience function that you can use to validate webhooks.

To validate webhooks:

1. Check out the [webhooks guide](https://replicate.com/docs/webhooks) to get started.
1. [Retrieve your webhook signing secret](https://replicate.com/docs/webhooks#retrieving-the-webhook-signing-key) and store it in your enviroment.
1. Update your webhook handler to call `validateWebhook(request, secret)`, where `request` is an instance of a [web-standard `Request` object](https://developer.mozilla.org/en-US/docs/Web/API/object, and `secret` is the signing secret for your environment.

Here's an example of how to validate webhooks using Next.js:

```js
import { NextResponse } from 'next/server';
import { validateWebhook } from 'replicate';

export async function POST(request) {
const secret = process.env.REPLICATE_WEBHOOK_SIGNING_SECRET;

if (!secret) {
console.log("Skipping webhook validation. To validate webhooks, set REPLICATE_WEBHOOK_SIGNING_SECRET")
const body = await request.json();
console.log(body);
return NextResponse.json({ detail: "Webhook received (but not validated)" }, { status: 200 });
}

const webhookIsValid = await validateWebhook(request.clone(), secret);

if (!webhookIsValid) {
return NextResponse.json({ detail: "Webhook is invalid" }, { status: 401 });
}

// process validated webhook here...
console.log("Webhook is valid!");
const body = await request.json();
console.log(body);

return NextResponse.json({ detail: "Webhook is valid" }, { status: 200 });
}
```

## TypeScript

The `Replicate` constructor and all `replicate.*` methods are fully typed.

Currently in order to support the module format used by `replicate` you'll need to set `esModuleInterop` to `true` in your tsconfig.json.

## API
Expand Down Expand Up @@ -973,29 +1022,17 @@ The `replicate.request()` method is used by the other methods
to interact with the Replicate API.
You can call this method directly to make other requests to the API.

## TypeScript

The `Replicate` constructor and all `replicate.*` methods are fully typed.

## Vendored Dependencies
## Troubleshooting

We have a few dependencies that have been bundled into the vendor directory rather than adding external npm dependencies.
### Predictions hanging in Next.js

These have been generated using bundlejs.com and copied into the appropriate directory along with the license and repository information.
Next.js App Router adds some extensions to `fetch` to make it cache responses. To disable this behavior, set the `cache` option to `"no-store"` on the Replicate client's fetch object:

* [eventsource-parser/stream](https://bundlejs.com/?bundle&q=eventsource-parser%40latest%2Fstream&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%2C%22platform%22%3A%22neutral%22%7D%7D)
* [streams-text-encoding/text-decoder-stream](https://bundlejs.com/?q=%40stardazed%2Fstreams-text-encoding&treeshake=%5B%7B+TextDecoderStream+%7D%5D&config=%7B%22esbuild%22%3A%7B%22format%22%3A%22cjs%22%2C%22minify%22%3Afalse%7D%7D)
```js
replicate = new Replicate({/*...*/})
replicate.fetch = (url, options) => {
return fetch(url, { ...options, cache: "no-store" });
};
```

> [!NOTE]
> The vendored implementation of `TextDecoderStream` requires
> the following patch to be applied to the output of bundlejs.com:
>
> ```diff
> constructor(label, options) {
> - this[decDecoder] = new TextDecoder(label, options);
> - this[decTransform] = new TransformStream(new TextDecodeTransformer(this[decDecoder]));
> + const decoder = new TextDecoder(label || "utf-8", options || {});
> + this[decDecoder] = decoder;
> + this[decTransform] = new TransformStream(new TextDecodeTransformer(decoder));
> }
> ```
Alternatively you can use Next.js [`noStore`](https://github.com/replicate/replicate-javascript/issues/136#issuecomment-1847442879) to opt out of caching for your component.
6 changes: 5 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
{
"$schema": "https://biomejs.dev/schemas/1.0.0/schema.json",
"files": {
"ignore": [".wrangler", "vendor/*"]
"ignore": [
".wrangler",
"node_modules",
"vendor/*"
]
},
"formatter": {
"indentStyle": "space",
Expand Down
19 changes: 19 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,21 @@ declare module "replicate" {
};
}

export interface FileObject {
id: string;
name: string;
content_type: string;
size: number;
etag: string;
checksum: string;
metadata: Record<string, unknown>;
created_at: string;
expires_at: string | null;
urls: {
get: string;
};
}

export interface Hardware {
sku: string;
name: string;
Expand Down Expand Up @@ -93,6 +108,8 @@ declare module "replicate" {

export type Training = Prediction;

export type FileEncodingStrategy = "default" | "upload" | "data-uri";

export interface Page<T> {
previous?: string;
next?: string;
Expand All @@ -119,12 +136,14 @@ declare module "replicate" {
input: Request | string,
init?: RequestInit
) => Promise<Response>;
fileEncodingStrategy?: FileEncodingStrategy;
});

auth: string;
userAgent?: string;
baseUrl?: string;
fetch: (input: Request | string, init?: RequestInit) => Promise<Response>;
fileEncodingStrategy: FileEncodingStrategy;

run(
identifier: `${string}/${string}` | `${string}/${string}:${string}`,
Expand Down
20 changes: 16 additions & 4 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class Replicate {
* @param {string} options.userAgent - Identifier of your app
* @param {string} [options.baseUrl] - Defaults to https://api.replicate.com/v1
* @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch`
* @param {"default" | "upload" | "data-uri"} [options.fileEncodingStrategy] - Determines the file encoding strategy to use
*/
constructor(options = {}) {
this.auth =
Expand All @@ -55,6 +56,7 @@ class Replicate {
options.userAgent || `replicate-javascript/${packageJSON.version}`;
this.baseUrl = options.baseUrl || "https://api.replicate.com/v1";
this.fetch = options.fetch || globalThis.fetch;
this.fileEncodingStrategy = options.fileEncodingStrategy ?? "default";

this.accounts = {
current: accounts.current.bind(this),
Expand Down Expand Up @@ -218,22 +220,32 @@ class Replicate {
url.searchParams.append(key, value);
}

const headers = {};
const headers = {
"Content-Type": "application/json",
"User-Agent": userAgent,
};
if (auth) {
headers["Authorization"] = `Bearer ${auth}`;
}
headers["Content-Type"] = "application/json";
headers["User-Agent"] = userAgent;
if (options.headers) {
for (const [key, value] of Object.entries(options.headers)) {
headers[key] = value;
}
}

let body = undefined;
if (data instanceof FormData) {
body = data;
// biome-ignore lint/performance/noDelete:
delete headers["Content-Type"]; // Use automatic content type header
} else if (data) {
body = JSON.stringify(data);
}

const init = {
method,
headers,
body: data ? JSON.stringify(data) : undefined,
body,
};

const shouldRetry =
Expand Down
Loading

0 comments on commit 887b6f9

Please sign in to comment.