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

[WIP] Error if a user tries to use durable objects with a service worker #634

Closed
wants to merge 5 commits into from
Closed
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
7 changes: 7 additions & 0 deletions .changeset/perfect-cycles-study.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"wrangler": patch
---

Error if a user tries to implement durable objects with a service worker

You can only implement Durable Objects in [Module Workers](https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/#advantages-of-migrating), so we should error if we discover that a user is trying to implement a durable object alongside a Service Worker.
171 changes: 171 additions & 0 deletions packages/wrangler/src/__tests__/entry.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
import { writeFile } from "fs/promises";
import path from "path";
import guessWorkerFormat from "../entry";
import { mockConsoleMethods } from "./helpers/mock-console";
import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";
import writeWranglerToml from "./helpers/write-wrangler-toml";

describe("entry", () => {
runInTempDir();
mockConsoleMethods();

it("should error when encountering a service worker with a durable object implementation", async () => {
await writeFile(
"./index.ts",
"addEventListener('fetch', () => \"hello, world!\")"
);
writeWranglerToml({
main: "index.ts",
durable_objects: {
bindings: [
{
name: "THIS_SHOULD_FAIL",
class_name: "ThisShouldFail",
script_name: "this-should-fail.js",
},
],
},
});

await expect(runWrangler("publish")).rejects
.toThrowErrorMatchingInlineSnapshot(`
"You cannot implement a Durable Object in a Service Worker, and should migrate to the Module format instead.
https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"
`);
});

it("should error when encountering a service worker with a per-environment durable object implementation", async () => {
await writeFile(
"./index.ts",
"addEventListener('fetch', () => \"hello, world!\")"
);
writeWranglerToml({
main: "index.ts",
env: {
ENV_1: {
durable_objects: {
bindings: [
{
name: "THIS_SHOULD_FAIL",
class_name: "ThisShouldFail",
script_name: "this-should-fail.js",
},
],
},
},
},
});

await expect(runWrangler("publish")).rejects
.toThrowErrorMatchingInlineSnapshot(`
"You cannot implement a Durable Object in a Service Worker, and should migrate to the Module format instead.
https://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"
`);
});

it("should allow service workers to bind to durable objects defined elsewhere", async () => {
await writeFile(
"./index.ts",
"addEventListener('fetch', () => \"hello, world!\")"
);
writeWranglerToml({
main: "index.ts",
durable_objects: {
bindings: [
{
name: "THIS_SHOULD_PASS",
class_name: "ThisShouldPass",
},
],
},
});

await expect(
runWrangler("publish")
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Did not login, quitting..."`
);
});

it("should allow service workers to bind to per-environment durable objects defined elsewhere", async () => {
await writeFile(
"./index.ts",
"addEventListener('fetch', () => \"hello, world!\")"
);
writeWranglerToml({
main: "index.ts",
env: {
ENV_1: {
durable_objects: {
bindings: [
{
name: "THIS_SHOULD_PASS",
class_name: "ThisShouldPass",
},
],
},
},
},
});

await expect(
runWrangler("publish")
).rejects.toThrowErrorMatchingInlineSnapshot(
`"Did not login, quitting..."`
);
});

describe("guess worker format", () => {
it('should detect a "modules" worker', async () => {
await writeFile("./index.ts", "export default {};");
// Note that this isn't actually a valid worker, because it's missing
// a fetch handler. Regardless, our heuristic is simply to check for exports.
const guess = await guessWorkerFormat(
path.join(process.cwd(), "./index.ts"),
process.cwd(),
undefined
);
expect(guess).toBe("modules");
});

it('should detect a "service-worker" worker', async () => {
await writeFile("./index.ts", "");
// Note that this isn't actually a valid worker, because it's missing
// a fetch listener. Regardless, our heuristic is simply to check for
// the lack of exports.
const guess = await guessWorkerFormat(
path.join(process.cwd(), "./index.ts"),
process.cwd(),
undefined
);
expect(guess).toBe("service-worker");
});

it("should throw an error when the hint doesn't match the guess (modules - service-worker)", async () => {
await writeFile("./index.ts", "export default {};");
await expect(
guessWorkerFormat(
path.join(process.cwd(), "./index.ts"),
process.cwd(),
"service-worker"
)
).rejects.toThrow(
"You configured this worker to be a 'service-worker', but the file you are trying to build appears to have ES module exports. Please pass `--format modules`, or simply remove the configuration."
);
});

it("should throw an error when the hint doesn't match the guess (service-worker - modules)", async () => {
await writeFile("./index.ts", "");
await expect(
guessWorkerFormat(
path.join(process.cwd(), "./index.ts"),
process.cwd(),
"modules"
)
).rejects.toThrow(
"You configured this worker to be 'modules', but the file you are trying to build doesn't export a handler. Please pass `--format service-worker`, or simply remove the configuration."
);
});
});
});
58 changes: 0 additions & 58 deletions packages/wrangler/src/__tests__/guess-worker-format.test.ts

This file was deleted.

23 changes: 23 additions & 0 deletions packages/wrangler/src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,13 @@ export async function getEntry(
directory,
args.format ?? config.build?.upload?.format
);

if (format === "service-worker" && hasDurableObjectImplementation(config)) {
throw new Error(
"You cannot implement a Durable Object in a Service Worker, and should migrate to the Module format instead.\nhttps://developers.cloudflare.com/workers/learning/migrating-to-module-workers/"
);
}

return { file, directory, format };
}

Expand Down Expand Up @@ -157,3 +164,19 @@ export function fileExists(filePath: string): boolean {
}
return false;
}

/**
* Returns true if the given config contains durable object bindings
*/
function hasDurableObjectImplementation(config: Config): boolean {
const topLevel = config.durable_objects.bindings.some(
(binding) => binding.script_name !== undefined
);
const nested = Object.values(config.env).some((env) =>
env.durable_objects.bindings.some(
(binding) => binding.script_name !== undefined
)
);

return topLevel || nested;
}