Skip to content

Commit 6589b09

Browse files
committed
feat: implement service environments + durable objects
Now that the APIs for getting migrations tags of services works as expected, this lands support for publishing durable objects to service environments, including migrations. It also removes the error we used to throw when attempting to use service envs + durable objects. Fixes #739
1 parent 0a79d75 commit 6589b09

File tree

4 files changed

+301
-29
lines changed

4 files changed

+301
-29
lines changed

.changeset/nasty-teachers-drop.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
feat: implement service environments + durable objects
6+
7+
Now that the APIs for getting migrations tags of services works as expected, this lands support for publishing durable objects to service environments, including migrations. It also removes the error we used to throw when attempting to use service envs + durable objects.
8+
9+
Fixes https://github.com/cloudflare/wrangler2/issues/739

packages/wrangler/src/__tests__/publish.test.ts

+252-16
Original file line numberDiff line numberDiff line change
@@ -2389,7 +2389,7 @@ export default{
23892389
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
23902390
);
23912391
mockSubDomainRequest();
2392-
mockScriptData({ scripts: [] }); // no previously uploaded scripts at all
2392+
mockLegacyScriptData({ scripts: [] }); // no previously uploaded scripts at all
23932393
mockUploadWorkerRequest({
23942394
expectedMigrations: {
23952395
new_tag: "v2",
@@ -2428,7 +2428,9 @@ export default{
24282428
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
24292429
);
24302430
mockSubDomainRequest();
2431-
mockScriptData({ scripts: [{ id: "test-name", migration_tag: "v1" }] });
2431+
mockLegacyScriptData({
2432+
scripts: [{ id: "test-name", migration_tag: "v1" }],
2433+
});
24322434
mockUploadWorkerRequest({
24332435
expectedMigrations: {
24342436
old_tag: "v1",
@@ -2473,7 +2475,9 @@ export default{
24732475
`export class SomeClass{}; export class SomeOtherClass{}; export class YetAnotherClass{}; export default {};`
24742476
);
24752477
mockSubDomainRequest();
2476-
mockScriptData({ scripts: [{ id: "test-name", migration_tag: "v3" }] });
2478+
mockLegacyScriptData({
2479+
scripts: [{ id: "test-name", migration_tag: "v3" }],
2480+
});
24772481
mockUploadWorkerRequest({
24782482
expectedMigrations: undefined,
24792483
});
@@ -2492,36 +2496,208 @@ export default{
24922496
});
24932497

24942498
describe("service environments", () => {
2495-
it("should error when using service environments + durable objects", async () => {
2499+
it("should publish all migrations on first publish", async () => {
24962500
writeWranglerToml({
24972501
durable_objects: {
24982502
bindings: [
24992503
{ name: "SOMENAME", class_name: "SomeClass" },
25002504
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
25012505
],
25022506
},
2503-
migrations: [{ tag: "v1", new_classes: ["SomeClass"] }],
2507+
migrations: [
2508+
{ tag: "v1", new_classes: ["SomeClass"] },
2509+
{ tag: "v2", new_classes: ["SomeOtherClass"] },
2510+
],
25042511
});
25052512
fs.writeFileSync(
25062513
"index.js",
25072514
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
25082515
);
2516+
mockSubDomainRequest();
2517+
mockServiceScriptData({}); // no scripts at all
2518+
mockUploadWorkerRequest({
2519+
legacyEnv: false,
2520+
expectedMigrations: {
2521+
new_tag: "v2",
2522+
steps: [
2523+
{ new_classes: ["SomeClass"] },
2524+
{ new_classes: ["SomeOtherClass"] },
2525+
],
2526+
},
2527+
});
25092528

2529+
await runWrangler("publish index.js --legacy-env false");
2530+
expect(std.out).toMatchInlineSnapshot(`
2531+
"Uploaded test-name (TIMINGS)
2532+
Published test-name (TIMINGS)
2533+
test-name.test-sub-domain.workers.dev"
2534+
`);
2535+
expect(std.err).toMatchInlineSnapshot(`""`);
2536+
expect(std.warn).toMatchInlineSnapshot(`
2537+
"▲ [WARNING] Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION.
2538+
2539+
"
2540+
`);
2541+
});
2542+
2543+
it("should publish all migrations on first publish (--env)", async () => {
2544+
writeWranglerToml({
2545+
durable_objects: {
2546+
bindings: [
2547+
{ name: "SOMENAME", class_name: "SomeClass" },
2548+
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
2549+
],
2550+
},
2551+
env: {
2552+
xyz: {
2553+
durable_objects: {
2554+
bindings: [
2555+
{ name: "SOMENAME", class_name: "SomeClass" },
2556+
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
2557+
],
2558+
},
2559+
},
2560+
},
2561+
migrations: [
2562+
{ tag: "v1", new_classes: ["SomeClass"] },
2563+
{ tag: "v2", new_classes: ["SomeOtherClass"] },
2564+
],
2565+
});
2566+
fs.writeFileSync(
2567+
"index.js",
2568+
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
2569+
);
25102570
mockSubDomainRequest();
2571+
mockServiceScriptData({ env: "xyz" }); // no scripts at all
2572+
mockUploadWorkerRequest({
2573+
legacyEnv: false,
2574+
env: "xyz",
2575+
expectedMigrations: {
2576+
new_tag: "v2",
2577+
steps: [
2578+
{ new_classes: ["SomeClass"] },
2579+
{ new_classes: ["SomeOtherClass"] },
2580+
],
2581+
},
2582+
});
25112583

2512-
await expect(
2513-
runWrangler("publish index.js --legacy-env false")
2514-
).rejects.toThrowErrorMatchingInlineSnapshot(
2515-
`"Publishing Durable Objects to a service environment is not currently supported. This is being tracked at https://github.com/cloudflare/wrangler2/issues/739"`
2584+
await runWrangler("publish index.js --legacy-env false --env xyz");
2585+
expect(std.out).toMatchInlineSnapshot(`
2586+
"Uploaded test-name (xyz) (TIMINGS)
2587+
Published test-name (xyz) (TIMINGS)
2588+
xyz.test-name.test-sub-domain.workers.dev"
2589+
`);
2590+
expect(std.err).toMatchInlineSnapshot(`""`);
2591+
expect(std.warn).toMatchInlineSnapshot(`
2592+
"▲ [WARNING] Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION.
2593+
2594+
"
2595+
`);
2596+
});
2597+
2598+
it("should use a script's current migration tag when publishing migrations", async () => {
2599+
writeWranglerToml({
2600+
durable_objects: {
2601+
bindings: [
2602+
{ name: "SOMENAME", class_name: "SomeClass" },
2603+
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
2604+
],
2605+
},
2606+
migrations: [
2607+
{ tag: "v1", new_classes: ["SomeClass"] },
2608+
{ tag: "v2", new_classes: ["SomeOtherClass"] },
2609+
],
2610+
});
2611+
fs.writeFileSync(
2612+
"index.js",
2613+
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
25162614
);
2615+
mockSubDomainRequest();
2616+
mockServiceScriptData({
2617+
script: { id: "test-name", migration_tag: "v1" },
2618+
});
2619+
mockUploadWorkerRequest({
2620+
legacyEnv: false,
2621+
expectedMigrations: {
2622+
old_tag: "v1",
2623+
new_tag: "v2",
2624+
steps: [
2625+
{
2626+
new_classes: ["SomeOtherClass"],
2627+
},
2628+
],
2629+
},
2630+
});
2631+
2632+
await runWrangler("publish index.js --legacy-env false");
25172633
expect(std).toMatchInlineSnapshot(`
25182634
Object {
25192635
"debug": "",
2520-
"err": "X [ERROR] Publishing Durable Objects to a service environment is not currently supported. This is being tracked at https://github.com/cloudflare/wrangler2/issues/739
2636+
"err": "",
2637+
"out": "Uploaded test-name (TIMINGS)
2638+
Published test-name (TIMINGS)
2639+
test-name.test-sub-domain.workers.dev",
2640+
"warn": "▲ [WARNING] Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION.
25212641
25222642
",
2523-
"out": "
2524-
If you think this is a bug then please create an issue at https://github.com/cloudflare/wrangler2/issues/new.",
2643+
}
2644+
`);
2645+
});
2646+
2647+
it("should use an environment's current migration tag when publishing migrations", async () => {
2648+
writeWranglerToml({
2649+
durable_objects: {
2650+
bindings: [
2651+
{ name: "SOMENAME", class_name: "SomeClass" },
2652+
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
2653+
],
2654+
},
2655+
env: {
2656+
xyz: {
2657+
durable_objects: {
2658+
bindings: [
2659+
{ name: "SOMENAME", class_name: "SomeClass" },
2660+
{ name: "SOMEOTHERNAME", class_name: "SomeOtherClass" },
2661+
],
2662+
},
2663+
},
2664+
},
2665+
migrations: [
2666+
{ tag: "v1", new_classes: ["SomeClass"] },
2667+
{ tag: "v2", new_classes: ["SomeOtherClass"] },
2668+
],
2669+
});
2670+
fs.writeFileSync(
2671+
"index.js",
2672+
`export class SomeClass{}; export class SomeOtherClass{}; export default {};`
2673+
);
2674+
mockSubDomainRequest();
2675+
mockServiceScriptData({
2676+
script: { id: "test-name", migration_tag: "v1" },
2677+
env: "xyz",
2678+
});
2679+
mockUploadWorkerRequest({
2680+
legacyEnv: false,
2681+
env: "xyz",
2682+
expectedMigrations: {
2683+
old_tag: "v1",
2684+
new_tag: "v2",
2685+
steps: [
2686+
{
2687+
new_classes: ["SomeOtherClass"],
2688+
},
2689+
],
2690+
},
2691+
});
2692+
2693+
await runWrangler("publish index.js --legacy-env false --env xyz");
2694+
expect(std).toMatchInlineSnapshot(`
2695+
Object {
2696+
"debug": "",
2697+
"err": "",
2698+
"out": "Uploaded test-name (xyz) (TIMINGS)
2699+
Published test-name (xyz) (TIMINGS)
2700+
xyz.test-name.test-sub-domain.workers.dev",
25252701
"warn": "▲ [WARNING] Service environments are in beta, and their behaviour is guaranteed to change in the future. DO NOT USE IN PRODUCTION.
25262702
25272703
",
@@ -2674,7 +2850,7 @@ export default{
26742850
],
26752851
});
26762852
mockSubDomainRequest();
2677-
mockScriptData({ scripts: [] });
2853+
mockLegacyScriptData({ scripts: [] });
26782854

26792855
await expect(runWrangler("publish index.js")).resolves.toBeUndefined();
26802856
expect(std.out).toMatchInlineSnapshot(`
@@ -3434,7 +3610,9 @@ export default{
34343610
`export class ExampleDurableObject {}; export default{};`
34353611
);
34363612
mockSubDomainRequest();
3437-
mockScriptData({ scripts: [{ id: "test-name", migration_tag: "v1" }] });
3613+
mockLegacyScriptData({
3614+
scripts: [{ id: "test-name", migration_tag: "v1" }],
3615+
});
34383616
mockUploadWorkerRequest({
34393617
expectedBindings: [
34403618
{
@@ -3508,7 +3686,9 @@ export default{
35083686
`export class ExampleDurableObject {}; export default{};`
35093687
);
35103688
mockSubDomainRequest();
3511-
mockScriptData({ scripts: [{ id: "test-name", migration_tag: "v1" }] });
3689+
mockLegacyScriptData({
3690+
scripts: [{ id: "test-name", migration_tag: "v1" }],
3691+
});
35123692
mockUploadWorkerRequest({
35133693
expectedType: "esm",
35143694
expectedBindings: [
@@ -4312,7 +4492,7 @@ function mockDeleteUnusedAssetsRequest(
43124492

43134493
type LegacyScriptInfo = { id: string; migration_tag?: string };
43144494

4315-
function mockScriptData(options: { scripts: LegacyScriptInfo[] }) {
4495+
function mockLegacyScriptData(options: { scripts: LegacyScriptInfo[] }) {
43164496
const { scripts } = options;
43174497
setMockResponse(
43184498
"/accounts/:accountId/workers/scripts",
@@ -4323,3 +4503,59 @@ function mockScriptData(options: { scripts: LegacyScriptInfo[] }) {
43234503
}
43244504
);
43254505
}
4506+
4507+
type DurableScriptInfo = { id: string; migration_tag?: string };
4508+
4509+
function mockServiceScriptData(options: {
4510+
script?: DurableScriptInfo;
4511+
scriptName?: string;
4512+
env?: string;
4513+
}) {
4514+
const { script } = options;
4515+
if (options.env) {
4516+
if (!script) {
4517+
setMockRawResponse(
4518+
"/accounts/:accountId/workers/services/:scriptName/environments/:envName",
4519+
"GET",
4520+
() => {
4521+
return createFetchResult(null, false, [
4522+
{ code: 10092, message: "workers.api.error.environment_not_found" },
4523+
]);
4524+
}
4525+
);
4526+
return;
4527+
}
4528+
setMockResponse(
4529+
"/accounts/:accountId/workers/services/:scriptName/environments/:envName",
4530+
"GET",
4531+
([_url, accountId, scriptName, envName]) => {
4532+
expect(accountId).toEqual("some-account-id");
4533+
expect(scriptName).toEqual(options.scriptName || "test-name");
4534+
expect(envName).toEqual(options.env);
4535+
return { script };
4536+
}
4537+
);
4538+
} else {
4539+
if (!script) {
4540+
setMockRawResponse(
4541+
"/accounts/:accountId/workers/services/:scriptName",
4542+
"GET",
4543+
() => {
4544+
return createFetchResult(null, false, [
4545+
{ code: 10090, message: "workers.api.error.service_not_found" },
4546+
]);
4547+
}
4548+
);
4549+
return;
4550+
}
4551+
setMockResponse(
4552+
"/accounts/:accountId/workers/services/:scriptName",
4553+
"GET",
4554+
([_url, accountId, scriptName]) => {
4555+
expect(accountId).toEqual("some-account-id");
4556+
expect(scriptName).toEqual(options.scriptName || "test-name");
4557+
return { default_environment: { script } };
4558+
}
4559+
);
4560+
}
4561+
}

packages/wrangler/src/config/validation.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1041,7 +1041,7 @@ const validateBindingsProperty =
10411041
diagnostics.warnings.push(
10421042
`The following bindings are at the top level, but not on "env.${envName}".\n` +
10431043
`This is not what you probably want, since "${field}" configuration is not inherited by environments.\n` +
1044-
`Please add a binding for each to "${fieldPath}.bindings".` +
1044+
`Please add a binding for each to "${fieldPath}.bindings":\n` +
10451045
missingBindings.map((name) => `- ${name}`).join("\n")
10461046
);
10471047
}

0 commit comments

Comments
 (0)