Skip to content

Commit 742c664

Browse files
authored
Support CloudFlare workers (#217)
* Update integration tests to exclude package-lock.json * Update CloudFlare worker example to test using streaming LLM * Run the CloudFlare worker integration test in CI * Add support for CloudFlare workers
1 parent cbfcc62 commit 742c664

File tree

15 files changed

+87
-1512
lines changed

15 files changed

+87
-1512
lines changed

.github/workflows/ci.yml

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
strategy:
3535
matrix:
3636
node-version: [18.x, 20.x]
37-
suite: [commonjs, esm, typescript]
37+
suite: [commonjs, esm, typescript, cloudflare-worker]
3838
# See supported Node.js release schedule at https://nodejs.org/en/about/previous-releases
3939

4040
env:
@@ -49,6 +49,8 @@ jobs:
4949
cache: "npm"
5050
# Build a production tarball and run the integration tests against it.
5151
- run: |
52+
test "${{ matrix.suite }}" = "cloudflare-worker" && echo "REPLICATE_API_TOKEN=${{ secrets.REPLICATE_API_TOKEN }}" > integration/${{ matrix.suite }}/.dev.vars
5253
PKG_TARBALL=$(npm --loglevel error pack)
54+
npm --prefix integration/${{ matrix.suite }} install
5355
npm --prefix integration/${{ matrix.suite }} install "file:/./$PKG_TARBALL"
5456
npm --prefix integration/${{ matrix.suite }} test

index.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ class Replicate {
4747
* @param {Function} [options.fetch] - Fetch function to use. Defaults to `globalThis.fetch`
4848
*/
4949
constructor(options = {}) {
50-
this.auth = options.auth || process.env.REPLICATE_API_TOKEN;
50+
this.auth =
51+
options.auth ||
52+
(typeof process !== "undefined" ? process.env.REPLICATE_API_TOKEN : null);
5153
this.userAgent =
5254
options.userAgent || `replicate-javascript/${packageJSON.version}`;
5355
this.baseUrl = options.baseUrl || "https://api.replicate.com/v1";
+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
.wrangler
22
node_modules
3+
.dev.vars
4+

integration/cloudflare-worker/.npmrc

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
package-lock=false
2+
+24-18
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,31 @@
11
import Replicate from "replicate";
22

3-
const replicate = new Replicate();
4-
53
export default {
6-
async fetch(_request, _env, _ctx) {
7-
const stream = new ReadableStream({
8-
async start(controller) {
9-
for await (const event of replicate.stream(
10-
"replicate/hello-world:5c7d5dc6dd8bf75c1acaa8565735e7986bc5b66206b55cca93cb72c9bf15ccaa",
11-
{
12-
input: {
13-
text: "Colin CloudFlare",
14-
},
15-
}
16-
)) {
17-
controller.enqueue(`${event}`);
4+
async fetch(_request, env, _ctx) {
5+
const replicate = new Replicate({ auth: env.REPLICATE_API_TOKEN });
6+
7+
try {
8+
const output = replicate.stream(
9+
"replicate/canary:30e22229542eb3f79d4f945dacb58d32001b02cc313ae6f54eef27904edf3272",
10+
{
11+
input: {
12+
text: "Colin CloudFlare",
13+
},
1814
}
19-
controller.close();
20-
},
21-
});
15+
);
16+
const stream = new ReadableStream({
17+
async start(controller) {
18+
for await (const event of output) {
19+
controller.enqueue(new TextEncoder().encode(`${event}`));
20+
}
21+
controller.enqueue(new TextEncoder().encode("\n"));
22+
controller.close();
23+
},
24+
});
2225

23-
return new Response(stream);
26+
return new Response(stream);
27+
} catch (err) {
28+
return Response("", { status: 500 });
29+
}
2430
},
2531
};
+26-18
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,36 @@
11
// https://developers.cloudflare.com/workers/wrangler/api/#unstable_dev
22
import { unstable_dev as dev } from "wrangler";
3-
import { test, after, before } from "node:test";
3+
import { test, after, before, describe } from "node:test";
44
import assert from "node:assert";
55

6-
let worker;
6+
/** @type {import("wrangler").UnstableDevWorker} */
7+
describe("CloudFlare Worker", () => {
8+
let worker;
79

8-
before(async () => {
9-
worker = await dev("index.js", {
10-
experimental: { disableExperimentalWarning: true },
10+
before(async () => {
11+
worker = await dev("./index.js", {
12+
port: 3000,
13+
experimental: { disableExperimentalWarning: true },
14+
});
1115
});
12-
});
1316

14-
after(async () => {
15-
if (!worker) {
16-
// If no worker the before hook failed to run and the process will hang.
17-
process.exit(1);
18-
}
19-
await worker.stop();
20-
});
17+
after(async () => {
18+
if (!worker) {
19+
// If no worker the before hook failed to run and the process will hang.
20+
process.exit(1);
21+
}
22+
await worker.stop();
23+
});
2124

22-
test("worker streams back a response", { timeout: 1000 }, async (t) => {
23-
const resp = await worker.fetch("/", { signal: t.signal });
24-
const text = await resp.text();
25+
test("worker streams back a response", { timeout: 1000 }, async () => {
26+
const resp = await worker.fetch();
27+
const text = await resp.text();
2528

26-
assert.ok(resp.ok, "status is 2xx");
27-
assert(text.length > 0, "body.length is greater than 0");
29+
assert.ok(resp.ok, "status is 2xx");
30+
assert(text.length > 0, "body.length is greater than 0");
31+
assert(
32+
text.includes("Colin CloudFlare"),
33+
"body includes stream characters"
34+
);
35+
});
2836
});

0 commit comments

Comments
 (0)