Skip to content

Commit

Permalink
fix: add warning about wrangler dev with remote Durable Objects (#687)
Browse files Browse the repository at this point in the history
Durable Objects that are being bound by `script_name` will not be isolated from the
live data during development with `wrangler dev`.
This change simply warns the developer about this, so that they can back out before
accidentally changing live data.

Fixes #319
  • Loading branch information
petebacondarwin authored Mar 25, 2022
1 parent 82ec7c2 commit 8f7ac7b
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 14 deletions.
12 changes: 12 additions & 0 deletions .changeset/four-bags-admire.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"wrangler": patch
---

fix: add warning about `wrangler dev` with remote Durable Objects

Durable Objects that are being bound by `script_name` will not be isolated from the
live data during development with `wrangler dev`.
This change simply warns the developer about this, so that they can back out before
accidentally changing live data.

Fixes #319
38 changes: 37 additions & 1 deletion packages/wrangler/src/__tests__/dev.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { runInTempDir } from "./helpers/run-in-tmp";
import { runWrangler } from "./helpers/run-wrangler";
import writeWranglerToml from "./helpers/write-wrangler-toml";

describe("Dev component", () => {
describe("wrangler dev", () => {
mockAccountId();
mockApiToken();
runInTempDir();
Expand Down Expand Up @@ -480,6 +480,42 @@ describe("Dev component", () => {
expect(std.err).toMatchInlineSnapshot(`""`);
});
});

describe("durable_objects", () => {
it("should warn if there is one or more remote Durable Object", async () => {
writeWranglerToml({
main: "index.js",
durable_objects: {
bindings: [
{ name: "NAME_1", class_name: "CLASS_1" },
{
name: "NAME_2",
class_name: "CLASS_2",
script_name: "SCRIPT_A",
},
{ name: "NAME_3", class_name: "CLASS_3" },
{
name: "NAME_4",
class_name: "CLASS_4",
script_name: "SCRIPT_B",
},
],
},
});
fs.writeFileSync("index.js", `export default {};`);
await runWrangler("dev");
expect((Dev as jest.Mock).mock.calls[0][0].ip).toEqual("localhost");
expect(std.out).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`
"WARNING: You have Durable Object bindings, which are not defined locally in the worker being developed.
Be aware that changes to the data stored in these Durable Objects will be permanent and affect the live instances.
Remote Durable Objects that are affected:
- {\\"name\\":\\"NAME_2\\",\\"class_name\\":\\"CLASS_2\\",\\"script_name\\":\\"SCRIPT_A\\"}
- {\\"name\\":\\"NAME_4\\",\\"class_name\\":\\"CLASS_4\\",\\"script_name\\":\\"SCRIPT_B\\"}"
`);
expect(std.err).toMatchInlineSnapshot(`""`);
});
});
});

function mockGetZones(domain: string, zones: { id: string }[] = []) {
Expand Down
50 changes: 37 additions & 13 deletions packages/wrangler/src/entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export type Entry = { file: string; directory: string; format: CfScriptFormat };
export async function getEntry(
args: { script?: string; format?: CfScriptFormat | undefined },
config: Config,
command: string
command: "dev" | "publish"
): Promise<Entry> {
let file: string;
let directory = process.cwd();
Expand Down Expand Up @@ -49,12 +49,24 @@ export async function getEntry(
args.format ?? config.build?.upload?.format
);

if (format === "service-worker" && hasDurableObjectImplementations(config)) {
const { localBindings, remoteBindings } =
partitionDurableObjectBindings(config);

if (command === "dev" && remoteBindings.length > 0) {
console.warn(
"WARNING: You have Durable Object bindings, which are not defined locally in the worker being developed.\n" +
"Be aware that changes to the data stored in these Durable Objects will be permanent and affect the live instances.\n" +
"Remote Durable Objects that are affected:\n" +
remoteBindings.map((b) => `- ${JSON.stringify(b)}`).join("\n")
);
}

if (format === "service-worker" && localBindings.length > 0) {
const errorMessage =
"You seem to be trying to use Durable Objects in a Worker written with Service Worker syntax.";
const addScriptName =
"You can use Durable Objects defined in other Workers by specifying a `script_name` in your wrangler.toml, where `script_name` is the name of the Worker that implements that Durable Object. For example:";
const addScriptNameExamples = generateAddScriptNameExamples(config);
const addScriptNameExamples = generateAddScriptNameExamples(localBindings);
const migrateText =
"Alternatively, migrate your worker to ES Module syntax to implement a Durable Object in this Worker:";
const migrateUrl =
Expand Down Expand Up @@ -174,29 +186,41 @@ export function fileExists(filePath: string): boolean {
return false;
}

type DurableObjectBindings = Config["durable_objects"]["bindings"];

/**
* Returns true if the given config contains Durable Object bindings that are implemented
* in this worker instead of being implemented elsewhere, and bound via a `script_name`
* property in wrangler.toml
* Groups the durable object bindings into two lists:
* those that are defined locally and those that refer to a durable object defined in another script.
*/
function hasDurableObjectImplementations(config: Config): boolean {
return config.durable_objects.bindings.some(
(binding) => binding.script_name === undefined
);
function partitionDurableObjectBindings(config: Config): {
localBindings: DurableObjectBindings;
remoteBindings: DurableObjectBindings;
} {
const localBindings: DurableObjectBindings = [];
const remoteBindings: DurableObjectBindings = [];
for (const binding of config.durable_objects.bindings) {
if (binding.script_name === undefined) {
localBindings.push(binding);
} else {
remoteBindings.push(binding);
}
}
return { localBindings, remoteBindings };
}

/**
* Generates some help text based on the Durable Object bindings in a given
* config indicating how the user can add a `script_name` field to bind an
* externally defined Durable Object.
*/
function generateAddScriptNameExamples(config: Config): string {
function generateAddScriptNameExamples(
localBindings: DurableObjectBindings
): string {
function exampleScriptName(binding_name: string): string {
return `${binding_name.toLowerCase().replaceAll("_", "-")}-worker`;
}

return config.durable_objects.bindings
.filter((binding) => binding.script_name === undefined)
return localBindings
.map(({ name, class_name }) => {
const script_name = exampleScriptName(name);
const currentBinding = `{ name = ${name}, class_name = ${class_name} }`;
Expand Down

0 comments on commit 8f7ac7b

Please sign in to comment.