Skip to content

Commit

Permalink
feat: bind a worker with [worker_namespaces] (#1377)
Browse files Browse the repository at this point in the history
This feature les you bind a worker to a dynamic dispatch namespaces, which may have other workers bound inside it. (See https://blog.cloudflare.com/workers-for-platforms/). Inside your `wrangler.toml`, you would add
```toml
[[worker_namespaces]]
binding = 'dispatcher' # available as env.dispatcher in your worker
namespace = 'namespace-name' # the name of the namespace being bound
```

Based on work by @aaronlisman in #1310
  • Loading branch information
threepointone authored Jul 5, 2022
1 parent 88f2702 commit a6f1cee
Show file tree
Hide file tree
Showing 11 changed files with 261 additions and 1 deletion.
15 changes: 15 additions & 0 deletions .changeset/sweet-wasps-clean.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
"wrangler": patch
---

feat: bind a worker with `[worker_namespaces]`

This feature les you bind a worker to a dynamic dispatch namespaces, which may have other workers bound inside it. (See https://blog.cloudflare.com/workers-for-platforms/). Inside your `wrangler.toml`, you would add

```toml
[[worker_namespaces]]
binding = 'dispatcher' # available as env.dispatcher in your worker
namespace = 'namespace-name' # the name of the namespace being bound
```

Based on work by @aaronlisman in https://github.com/cloudflare/wrangler2/pull/1310
108 changes: 108 additions & 0 deletions packages/wrangler/src/__tests__/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ describe("normalizeAndValidateConfig()", () => {

expect(config).toEqual({
account_id: undefined,
assets: undefined,
build: {
command: undefined,
cwd: undefined,
Expand Down Expand Up @@ -55,6 +56,7 @@ describe("normalizeAndValidateConfig()", () => {
unsafe: {
bindings: [],
},
worker_namespaces: [],
usage_model: undefined,
vars: {},
define: {},
Expand Down Expand Up @@ -1839,6 +1841,112 @@ describe("normalizeAndValidateConfig()", () => {
});
});

describe("[worker_namespaces]", () => {
it("should log an experimental warning when worker_namespaces is used", () => {
const { config, diagnostics } = normalizeAndValidateConfig(
{
worker_namespaces: [
{
binding: "BINDING_1",
namespace: "NAMESPACE_1",
},
],
} as unknown as RawConfig,
undefined,
{ env: undefined }
);
expect(config).toEqual(
expect.not.objectContaining({ worker_namespaces: expect.anything })
);
expect(diagnostics.hasWarnings()).toBe(true);
expect(diagnostics.hasErrors()).toBe(false);
expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- \\"worker_namespaces\\" fields are experimental and may change or break at any time."
`);
});

it("should error if worker_namespaces is not an array", () => {
const { config, diagnostics } = normalizeAndValidateConfig(
{
worker_namespaces: "just a string",
} as unknown as RawConfig,
undefined,
{ env: undefined }
);

expect(config).toEqual(
expect.not.objectContaining({ worker_namespaces: expect.anything })
);
expect(diagnostics.hasWarnings()).toBe(true);
expect(diagnostics.hasErrors()).toBe(true);
expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- \\"worker_namespaces\\" fields are experimental and may change or break at any time."
`);
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- The field \\"worker_namespaces\\" should be an array but got \\"just a string\\"."
`);
});

it("should error on non valid worker_namespaces", () => {
const { config, diagnostics } = normalizeAndValidateConfig(
{
worker_namespaces: [
"a string",
123,
{
binding: 123,
namespace: 456,
},
{
binding: "WORKER_NAMESPACE_BINDING_1",
namespace: 456,
},
// this one is valid
{
binding: "WORKER_NAMESPACE_BINDING_1",
namespace: "WORKER_NAMESPACE_BINDING_NAMESPACE_1",
},
{
binding: 123,
namespace: "WORKER_NAMESPACE_BINDING_SERVICE_1",
},
{
binding: 123,
service: 456,
},
],
} as unknown as RawConfig,
undefined,
{ env: undefined }
);
expect(config).toEqual(
expect.not.objectContaining({
worker_namespaces: expect.anything,
})
);
expect(diagnostics.hasWarnings()).toBe(true);
expect(diagnostics.hasErrors()).toBe(true);
expect(diagnostics.renderWarnings()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- \\"worker_namespaces\\" fields are experimental and may change or break at any time."
`);
expect(diagnostics.renderErrors()).toMatchInlineSnapshot(`
"Processing wrangler configuration:
- \\"worker_namespaces[0]\\" binding should be objects, but got \\"a string\\"
- \\"worker_namespaces[1]\\" binding should be objects, but got 123
- \\"worker_namespaces[2]\\" should have a string \\"binding\\" field but got {\\"binding\\":123,\\"namespace\\":456}.
- \\"worker_namespaces[2]\\" should have a string \\"namespace\\" field but got {\\"binding\\":123,\\"namespace\\":456}.
- \\"worker_namespaces[3]\\" should have a string \\"namespace\\" field but got {\\"binding\\":\\"WORKER_NAMESPACE_BINDING_1\\",\\"namespace\\":456}.
- \\"worker_namespaces[5]\\" should have a string \\"binding\\" field but got {\\"binding\\":123,\\"namespace\\":\\"WORKER_NAMESPACE_BINDING_SERVICE_1\\"}.
- \\"worker_namespaces[6]\\" should have a string \\"binding\\" field but got {\\"binding\\":123,\\"service\\":456}.
- \\"worker_namespaces[6]\\" should have a string \\"namespace\\" field but got {\\"binding\\":123,\\"service\\":456}."
`);
});
});

describe("[unsafe.bindings]", () => {
it("should error if unsafe is an array", () => {
const { config, diagnostics } = normalizeAndValidateConfig(
Expand Down
43 changes: 43 additions & 0 deletions packages/wrangler/src/__tests__/publish.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5303,6 +5303,48 @@ addEventListener('fetch', event => {});`
});
});

describe("[worker_namespaces]", () => {
it("should support bindings to a worker namespace", async () => {
writeWranglerToml({
worker_namespaces: [
{
binding: "foo",
namespace: "Foo",
},
],
});
writeWorkerSource();
mockSubDomainRequest();
mockUploadWorkerRequest({
expectedBindings: [
{
type: "namespace",
name: "foo",
namespace: "Foo",
},
],
});
await runWrangler("publish index.js");
expect(std.out).toMatchInlineSnapshot(`
"Your worker has access to the following bindings:
- Worker Namespaces:
- foo: Foo
Total Upload: 0xx KiB / gzip: 0xx KiB
Uploaded test-name (TIMINGS)
Published test-name (TIMINGS)
test-name.test-sub-domain.workers.dev"
`);
expect(std.err).toMatchInlineSnapshot(`""`);
expect(std.warn).toMatchInlineSnapshot(`
"▲ [WARNING] Processing wrangler.toml configuration:
- \\"worker_namespaces\\" fields are experimental and may change or break at any time.
"
`);
});
});

describe("[unsafe]", () => {
it("should warn if using unsafe bindings", async () => {
writeWranglerToml({
Expand Down Expand Up @@ -5962,6 +6004,7 @@ addEventListener('fetch', event => {});`
}
`);
});

it("should print the bundle size, with API errors", async () => {
setMockRawResponse(
"/accounts/:accountId/workers/scripts/:scriptName",
Expand Down
16 changes: 16 additions & 0 deletions packages/wrangler/src/config/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,22 @@ interface EnvironmentInheritable {
*/
node_compat: boolean | undefined;

/**
* Specifies namespace bindings that are bound to this Worker environment.
*
* NOTE: This field is not automatically inherited from the top level environment,
* and so must be specified in every named environment.
*
* @default `[]`
* @nonInheritable
*/
worker_namespaces: {
/** The binding name used to refer to the bound service. */
binding: string;
/** The namespace to bind to. */
namespace: string;
}[];

/**
* TODO: remove this as it has been deprecated.
*
Expand Down
13 changes: 13 additions & 0 deletions packages/wrangler/src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
unsafe,
vars,
wasm_modules,
worker_namespaces,
} = bindings;

if (data_blobs !== undefined && Object.keys(data_blobs).length > 0) {
Expand Down Expand Up @@ -205,6 +206,18 @@ export function printBindings(bindings: CfWorkerInit["bindings"]) {
});
}

if (worker_namespaces !== undefined && worker_namespaces.length > 0) {
output.push({
type: "Worker Namespaces",
entries: worker_namespaces.map(({ binding, namespace }) => {
return {
key: binding,
value: namespace,
};
}),
});
}

if (output.length === 0) {
return;
}
Expand Down
44 changes: 44 additions & 0 deletions packages/wrangler/src/config/validation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,7 @@ function normalizeAndValidateEnvironment(

experimental(diagnostics, rawEnv, "unsafe");
experimental(diagnostics, rawEnv, "services");
experimental(diagnostics, rawEnv, "worker_namespaces");

const route = normalizeAndValidateRoute(diagnostics, topLevelEnv, rawEnv);

Expand Down Expand Up @@ -1017,6 +1018,16 @@ function normalizeAndValidateEnvironment(
validateBindingArray(envName, validateServiceBinding),
[]
),
worker_namespaces: notInheritable(
diagnostics,
topLevelEnv,
rawConfig,
rawEnv,
envName,
"worker_namespaces",
validateBindingArray(envName, validateWorkerNamespaceBinding),
[]
),
unsafe: notInheritable(
diagnostics,
topLevelEnv,
Expand Down Expand Up @@ -1664,6 +1675,7 @@ const validateBindingsHaveUniqueNames = (

return !hasDuplicates;
};

const validateServiceBinding: ValidatorFn = (diagnostics, field, value) => {
if (typeof value !== "object" || value === null) {
diagnostics.errors.push(
Expand Down Expand Up @@ -1699,3 +1711,35 @@ const validateServiceBinding: ValidatorFn = (diagnostics, field, value) => {
}
return isValid;
};

const validateWorkerNamespaceBinding: ValidatorFn = (
diagnostics,
field,
value
) => {
if (typeof value !== "object" || value === null) {
diagnostics.errors.push(
`"${field}" binding should be objects, but got ${JSON.stringify(value)}`
);
return false;
}
let isValid = true;
// Worker namespace bindings must have a binding, and a namespace.
if (!isRequiredProperty(value, "binding", "string")) {
diagnostics.errors.push(
`"${field}" should have a string "binding" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}
if (!isRequiredProperty(value, "namespace", "string")) {
diagnostics.errors.push(
`"${field}" should have a string "namespace" field but got ${JSON.stringify(
value
)}.`
);
isValid = false;
}
return isValid;
};
9 changes: 9 additions & 0 deletions packages/wrangler/src/create-worker-upload-form.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface WorkerMetadata {
}
| { type: "r2_bucket"; name: string; bucket_name: string }
| { type: "service"; name: string; service: string; environment?: string }
| { type: "namespace"; name: string; namespace: string }
)[];
}

Expand Down Expand Up @@ -116,6 +117,14 @@ export function createWorkerUploadForm(worker: CfWorkerInit): FormData {
});
});

bindings.worker_namespaces?.forEach(({ binding, namespace }) => {
metadataBindings.push({
name: binding,
type: "namespace",
namespace,
});
});

for (const [name, filePath] of Object.entries(bindings.wasm_modules || {})) {
metadataBindings.push({
name,
Expand Down
6 changes: 5 additions & 1 deletion packages/wrangler/src/dev.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {

import type { Config } from "./config";
import type { Route } from "./config/environment";
import type { CfWorkerInit } from "./worker";
import type { RequestInit } from "undici";
import type { Argv, ArgumentsCamelCase } from "yargs";

Expand Down Expand Up @@ -371,7 +372,9 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
}

// eslint-disable-next-line no-inner-declarations
async function getBindings(configParam: Config) {
async function getBindings(
configParam: Config
): Promise<CfWorkerInit["bindings"]> {
return {
kv_namespaces: configParam.kv_namespaces?.map(
({ binding, preview_id, id: _id }) => {
Expand Down Expand Up @@ -415,6 +418,7 @@ export async function startDev(args: ArgumentsCamelCase<DevArgs>) {
};
}
),
worker_namespaces: configParam.worker_namespaces,
services: configParam.services,
unsafe: configParam.unsafe?.bindings,
};
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -916,6 +916,7 @@ function createCLIParser(argv: string[]) {
wasm_modules: {},
text_blobs: {},
data_blobs: {},
worker_namespaces: [],
unsafe: [],
},
modules: [],
Expand Down
1 change: 1 addition & 0 deletions packages/wrangler/src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,7 @@ See https://developers.cloudflare.com/workers/platform/compatibility-dates for m
durable_objects: config.durable_objects,
r2_buckets: config.r2_buckets,
services: config.services,
worker_namespaces: config.worker_namespaces,
unsafe: config.unsafe?.bindings,
};

Expand Down
Loading

0 comments on commit a6f1cee

Please sign in to comment.