-
Notifications
You must be signed in to change notification settings - Fork 778
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
implement
getBindingsProxy
utility (#4523)
* implement getBindingsProxy utility * add new get-bindings-proxy fixture to test the `getBindingsProxy` utility --------- Co-authored-by: Pete Bacon Darwin <[email protected]>
- Loading branch information
1 parent
b79e93a
commit 9f96f28
Showing
26 changed files
with
736 additions
and
36 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
--- | ||
"wrangler": minor | ||
--- | ||
|
||
Add new `getBindingsProxy` utility to the wrangler package | ||
|
||
The new utility is part of wrangler's JS API (it is not part of the wrangler CLI) and its use is to provide proxy objects to bindings, such objects can be used in Node.js code as if they were actual bindings | ||
|
||
The utility reads the `wrangler.toml` file present in the current working directory in order to discern what bindings should be available (a `wrangler.json` file can be used too, as well as config files with custom paths). | ||
|
||
## Example | ||
|
||
Assuming that in the current working directory there is a `wrangler.toml` file with the following | ||
content: | ||
|
||
``` | ||
[[kv_namespaces]] | ||
binding = "MY_KV" | ||
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | ||
``` | ||
|
||
The utility could be used in a nodejs script in the following way: | ||
|
||
```js | ||
import { getBindingsProxy } from "wrangler"; | ||
|
||
// we use the utility to get the bindings proxies | ||
const { bindings, dispose } = await getBindingsProxy(); | ||
|
||
// we get access to the KV binding proxy | ||
const myKv = bindings.MY_KV; | ||
// we can then use the proxy in the same exact way we'd use the | ||
// KV binding in the workerd runtime, without any API discrepancies | ||
const kvValue = await myKv.get("my-kv-key"); | ||
|
||
console.log(` | ||
KV Value = ${kvValue} | ||
`); | ||
|
||
// we need to dispose of the underlying child process in order for this nodejs script to properly terminate | ||
await dispose(); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name = "get-bindings-proxy-fixture" | ||
main = "src/index.ts" | ||
compatibility_date = "2023-11-21" | ||
|
||
[vars] | ||
MY_VAR = "my-var-value-from-a-custom-toml" | ||
MY_JSON_VAR = { test = true, customToml = true } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
{ | ||
"name": "get-bindings-proxy-fixture", | ||
"private": true, | ||
"description": "A test for the getBindingsProxy utility", | ||
"scripts": { | ||
"test": "vitest run", | ||
"test:watch": "vitest", | ||
"type:tests": "tsc --noEmit" | ||
}, | ||
"devDependencies": { | ||
"@cloudflare/workers-tsconfig": "workspace:*", | ||
"@cloudflare/workers-types": "^4.20221111.1", | ||
"wrangler": "workspace:*" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,200 @@ | ||
import { describe, expect, it, beforeAll, afterAll } from "vitest"; | ||
import { | ||
type UnstableDevWorker, | ||
unstable_dev, | ||
getBindingsProxy, | ||
} from "wrangler"; | ||
import { | ||
R2Bucket, | ||
type KVNamespace, | ||
Fetcher, | ||
D1Database, | ||
DurableObjectNamespace, | ||
} from "@cloudflare/workers-types"; | ||
import { readdir, rm } from "fs/promises"; | ||
import path from "path"; | ||
|
||
type Bindings = { | ||
MY_VAR: string; | ||
MY_JSON_VAR: Object; | ||
MY_SERVICE_A: Fetcher; | ||
MY_SERVICE_B: Fetcher; | ||
MY_KV: KVNamespace; | ||
MY_DO_A: DurableObjectNamespace; | ||
MY_DO_B: DurableObjectNamespace; | ||
MY_BUCKET: R2Bucket; | ||
MY_D1: D1Database; | ||
}; | ||
|
||
const wranglerTomlFilePath = path.join(__dirname, "..", "wrangler.toml"); | ||
|
||
describe("getBindingsProxy", () => { | ||
let devWorkers: UnstableDevWorker[]; | ||
|
||
beforeAll(async () => { | ||
devWorkers = await startWorkers(); | ||
|
||
await rm(path.join(__dirname, "..", ".wrangler"), { | ||
force: true, | ||
recursive: true, | ||
}); | ||
}); | ||
|
||
afterAll(async () => { | ||
await Promise.allSettled(devWorkers.map((i) => i.stop())); | ||
}); | ||
|
||
it("correctly obtains var bindings", async () => { | ||
const { bindings, dispose } = await getBindingsProxy<Bindings>({ | ||
configPath: wranglerTomlFilePath, | ||
}); | ||
const { MY_VAR, MY_JSON_VAR } = bindings; | ||
expect(MY_VAR).toEqual("my-var-value"); | ||
expect(MY_JSON_VAR).toEqual({ | ||
test: true, | ||
}); | ||
await dispose(); | ||
}); | ||
|
||
it("correctly reads a toml from a custom path", async () => { | ||
const { bindings, dispose } = await getBindingsProxy<Bindings>({ | ||
configPath: path.join( | ||
__dirname, | ||
"..", | ||
"custom-toml", | ||
"path", | ||
"test-toml" | ||
), | ||
}); | ||
const { MY_VAR, MY_JSON_VAR } = bindings; | ||
expect(MY_VAR).toEqual("my-var-value-from-a-custom-toml"); | ||
expect(MY_JSON_VAR).toEqual({ | ||
test: true, | ||
customToml: true, | ||
}); | ||
await dispose(); | ||
}); | ||
|
||
it("correctly reads a json config file", async () => { | ||
const { bindings, dispose } = await getBindingsProxy<Bindings>({ | ||
configPath: path.join(__dirname, "..", "wrangler.json"), | ||
}); | ||
const { MY_VAR, MY_JSON_VAR } = bindings; | ||
expect(MY_VAR).toEqual("my-var-value-from-a-json-config-file"); | ||
expect(MY_JSON_VAR).toEqual({ | ||
test: true, | ||
fromJson: true, | ||
}); | ||
await dispose(); | ||
}); | ||
|
||
it("provides service bindings to external local workers", async () => { | ||
const { bindings, dispose } = await getBindingsProxy<Bindings>({ | ||
configPath: wranglerTomlFilePath, | ||
}); | ||
const { MY_SERVICE_A, MY_SERVICE_B } = bindings; | ||
await testServiceBinding(MY_SERVICE_A, "Hello World from hello-worker-a"); | ||
await testServiceBinding(MY_SERVICE_B, "Hello World from hello-worker-b"); | ||
await dispose(); | ||
}); | ||
|
||
it("correctly obtains functioning KV bindings", async () => { | ||
const { bindings, dispose } = await getBindingsProxy<Bindings>({ | ||
configPath: wranglerTomlFilePath, | ||
}); | ||
const { MY_KV } = bindings; | ||
let numOfKeys = (await MY_KV.list()).keys.length; | ||
expect(numOfKeys).toBe(0); | ||
await MY_KV.put("my-key", "my-value"); | ||
numOfKeys = (await MY_KV.list()).keys.length; | ||
expect(numOfKeys).toBe(1); | ||
const value = await MY_KV.get("my-key"); | ||
expect(value).toBe("my-value"); | ||
await dispose(); | ||
}); | ||
|
||
it("correctly obtains functioning DO bindings (provided by external local workers)", async () => { | ||
const { bindings, dispose } = await getBindingsProxy<Bindings>({ | ||
configPath: wranglerTomlFilePath, | ||
}); | ||
const { MY_DO_A, MY_DO_B } = bindings; | ||
await testDoBinding(MY_DO_A, "Hello from DurableObject A"); | ||
await testDoBinding(MY_DO_B, "Hello from DurableObject B"); | ||
await dispose(); | ||
}); | ||
|
||
it("correctly obtains functioning R2 bindings", async () => { | ||
const { bindings, dispose } = await getBindingsProxy<Bindings>({ | ||
configPath: wranglerTomlFilePath, | ||
}); | ||
const { MY_BUCKET } = bindings; | ||
let numOfObjects = (await MY_BUCKET.list()).objects.length; | ||
expect(numOfObjects).toBe(0); | ||
await MY_BUCKET.put("my-object", "my-value"); | ||
numOfObjects = (await MY_BUCKET.list()).objects.length; | ||
expect(numOfObjects).toBe(1); | ||
const value = await MY_BUCKET.get("my-object"); | ||
expect(await value?.text()).toBe("my-value"); | ||
await dispose(); | ||
}); | ||
|
||
it("correctly obtains functioning D1 bindings", async () => { | ||
const { bindings, dispose } = await getBindingsProxy<Bindings>({ | ||
configPath: wranglerTomlFilePath, | ||
}); | ||
const { MY_D1 } = bindings; | ||
await MY_D1.exec( | ||
`CREATE TABLE IF NOT EXISTS users ( id integer PRIMARY KEY AUTOINCREMENT, name text NOT NULL )` | ||
); | ||
const stmt = MY_D1.prepare("insert into users (name) values (?1)"); | ||
await MY_D1.batch([ | ||
stmt.bind("userA"), | ||
stmt.bind("userB"), | ||
stmt.bind("userC"), | ||
]); | ||
const { results } = await MY_D1.prepare( | ||
"SELECT name FROM users LIMIT 5" | ||
).all(); | ||
expect(results).toEqual([ | ||
{ name: "userA" }, | ||
{ name: "userB" }, | ||
{ name: "userC" }, | ||
]); | ||
await dispose(); | ||
}); | ||
}); | ||
|
||
/** | ||
* Starts all the workers present in the `workers` directory using `unstable_dev` | ||
* | ||
* @returns the workers' UnstableDevWorker instances | ||
*/ | ||
async function startWorkers(): Promise<UnstableDevWorker[]> { | ||
const workersDirPath = path.join(__dirname, "..", "workers"); | ||
const workers = await readdir(workersDirPath); | ||
return await Promise.all( | ||
workers.map((workerName) => { | ||
const workerPath = path.join(workersDirPath, workerName); | ||
return unstable_dev(path.join(workerPath, "index.ts"), { | ||
config: path.join(workerPath, "wrangler.toml"), | ||
}); | ||
}) | ||
); | ||
} | ||
|
||
async function testServiceBinding(binding: Fetcher, expectedResponse: string) { | ||
const resp = await binding.fetch("http://0.0.0.0"); | ||
const respText = await resp.text(); | ||
expect(respText).toBe(expectedResponse); | ||
} | ||
|
||
async function testDoBinding( | ||
binding: DurableObjectNamespace, | ||
expectedResponse: string | ||
) { | ||
const durableObjectId = binding.idFromName("__my-do__"); | ||
const doStub = binding.get(durableObjectId); | ||
const doResp = await doStub.fetch("http://0.0.0.0"); | ||
const doRespText = await doResp.text(); | ||
expect(doRespText).toBe(expectedResponse); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
{ | ||
"include": [ | ||
"workers/hello-worker-a", | ||
"module-worker-b", | ||
"service-worker-a", | ||
"module-worker-c", | ||
"module-worker-d", | ||
"pages-functions-app" | ||
], | ||
"compilerOptions": { | ||
"target": "ES2020", | ||
"module": "CommonJS", | ||
"lib": ["ES2020"], | ||
"types": ["@cloudflare/workers-types"] | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import { defineConfig } from "vitest/config"; | ||
|
||
export default defineConfig({ | ||
test: { | ||
testTimeout: 25_000, | ||
hookTimeout: 25_000, | ||
teardownTimeout: 25_000, | ||
useAtomics: true, | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default { | ||
async fetch(): Promise<Response> { | ||
return new Response("Hello World from do-worker-a"); | ||
}, | ||
}; | ||
|
||
export class DurableObjectClass { | ||
async fetch() { | ||
return new Response("Hello from DurableObject A"); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
fixtures/get-bindings-proxy/workers/do-worker-a/wrangler.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
name = "do-worker-a" | ||
|
||
[[durable_objects.bindings]] | ||
name = "MY_DO" | ||
class_name = "DurableObjectClass" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
export default { | ||
async fetch(): Promise<Response> { | ||
return new Response("Hello World from do-worker-a"); | ||
}, | ||
}; | ||
|
||
export class DurableObjectClass { | ||
async fetch() { | ||
return new Response("Hello from DurableObject B"); | ||
} | ||
} |
5 changes: 5 additions & 0 deletions
5
fixtures/get-bindings-proxy/workers/do-worker-b/wrangler.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
name = "do-worker-b" | ||
|
||
[[durable_objects.bindings]] | ||
name = "MY_DO" | ||
class_name = "DurableObjectClass" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export default { | ||
fetch() { | ||
return new Response("Hello World from hello-worker-a"); | ||
}, | ||
}; |
1 change: 1 addition & 0 deletions
1
fixtures/get-bindings-proxy/workers/hello-worker-a/wrangler.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
name = "hello-worker-a" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export default { | ||
fetch() { | ||
return new Response("Hello World from hello-worker-b"); | ||
}, | ||
}; |
1 change: 1 addition & 0 deletions
1
fixtures/get-bindings-proxy/workers/hello-worker-b/wrangler.toml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
name = "hello-worker-b" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
{ | ||
"name": "get-bindings-proxy-fixture", | ||
"main": "src/index.ts", | ||
"vars": { | ||
"MY_VAR": "my-var-value-from-a-json-config-file", | ||
"MY_JSON_VAR": { | ||
"test": true, | ||
"fromJson": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
name = "get-bindings-proxy-fixture" | ||
main = "src/index.ts" | ||
compatibility_date = "2023-11-21" | ||
|
||
services = [ | ||
{ binding = "MY_SERVICE_A", service = "hello-worker-a" }, | ||
{ binding = "MY_SERVICE_B", service = "hello-worker-b" } | ||
] | ||
|
||
[vars] | ||
MY_VAR = "my-var-value" | ||
MY_JSON_VAR = { test = true } | ||
|
||
[[kv_namespaces]] | ||
binding = "MY_KV" | ||
id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" | ||
|
||
[[r2_buckets]] | ||
binding = "MY_BUCKET" | ||
bucket_name = "my-bucket" | ||
|
||
[durable_objects] | ||
bindings = [ | ||
{ name = "MY_DO_A", script_name = "do-worker-a", class_name = "DurableObjectClass" }, | ||
{ name = "MY_DO_B", script_name = "do-worker-b", class_name = "DurableObjectClass" } | ||
] | ||
|
||
[[d1_databases]] | ||
binding = "MY_D1" | ||
database_name = "test-db" | ||
database_id = "000000000-0000-0000-0000-000000000000" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.