Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/leaysgur/cfw-bindings-wrangler-bridge
This bridge makes it possible to interact with remote Cloudflare Workers bindings(like KV, D1, etc...) from anywhere you want.
https://github.com/leaysgur/cfw-bindings-wrangler-bridge
cloudflare d1 kv r2 workers wrangler
Last synced: 11 days ago
JSON representation
This bridge makes it possible to interact with remote Cloudflare Workers bindings(like KV, D1, etc...) from anywhere you want.
- Host: GitHub
- URL: https://github.com/leaysgur/cfw-bindings-wrangler-bridge
- Owner: leaysgur
- Created: 2023-06-22T08:01:28.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2024-05-07T05:04:18.000Z (6 months ago)
- Last Synced: 2024-10-14T19:13:53.348Z (23 days ago)
- Topics: cloudflare, d1, kv, r2, workers, wrangler
- Language: JavaScript
- Homepage:
- Size: 295 KB
- Stars: 43
- Watchers: 1
- Forks: 2
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
Awesome Lists containing this project
README
# cfw-bindings-wrangler-bridge
This bridge makes it possible to interact with **remote** Cloudflare Workers bindings(like KV, D1, etc...) **from anywhere** you want.
For example,
- Use remote data with Vite based meta frameworks local development
- CLI tools to sync remote data to local, copy remote data to remote
- Static Site Generation, Pre-rendering with remote data
- etc...Many possibilities are unlocked now! 🔓
To understand the motivation in detail, see also my talk [slides](https://leaysgur.github.io/slides/cloudflare_workers_tech_talks-2).
> [!IMPORTANT]
> If your purpose is to mock bindings only for closed, local development and no initial data is needed(or can be easily prepared), you may not need this bridge.
>
> For these cases, we recommend using mock APIs like:
>
> - [`miniflare@3`](https://github.com/cloudflare/workers-sdk/tree/main/packages/miniflare#class-miniflare) API
> - Some of frameworks may have its own support for `miniflare` in their adapters like [SolidStart](https://github.com/solidjs/solid-start/tree/main/packages/start-cloudflare-pages)
> - [`getPlatformProxy()`](https://developers.cloudflare.com/workers/wrangler/api/#getplatformproxy) from `wrangler` module
>
> If those do not match for your case or you really need the remote data, please go ahead. 🤤## How it works
This bridge has 2 components.
- Module: Mock module to be `import`ed into your application
- written as pure ESM
- run on any environment
- Worker: Proxy worker to be invoked by the bridge module
- hosted by `wrangler dev` or `unstable_dev()` in advance
- run on Cloudflare Workers environmentBridge module itself is platform agnostic, you can use it on any platform|environment.
## Install & Prerequisite
Install bridge as usual.
```sh
npm install -D cfw-bindings-wrangler-bridge
```And make sure to prepare `wrangler.toml` for your needs.
## Usage
There are 2 options.
- 🅰️ Bridge worker w/ external `wrangler dev` process
- 🅱️ Bridge worker w/ `unstable_dev()` API by `wrangler` packageIt's up to your usecase.
### 🅰️ With external `wrangler dev` process
```sh
wrangler dev ./path/to/node_modules/cfw-bindings-wrangler-bridge/worker/index.js --remote
# Worker will be running on `http://127.0.0.1:8787` by default
```Of course you can interact with local environment by omitting `--remote`. All the other options(like `--port`, `--persist-to`) are also available.
Then, create bridge modules and use them anywhere in your code.
```js
import { KVNamespace$ } from "cfw-bindings-wrangler-bridge";// Default is bound to `http://127.0.0.1:8787`
const MY_KV = new KVNamespace$("MY_KV");
// Or specify origin
// const MY_KV = new KVNamespace$("MY_KV", {
// bridgeWorkerOrigin: "http://localhsot:8686",
// });// ✌️ This is remote KV!
await MY_KV.put("foo", "bar");
await MY_KV.get("foo"); // "bar"
```This is isomorphoc approach, your module can be run on everywhere(Node.js, Bun, Browsers, etc).
Setups can be done in synchronous, it keeps your code as simple as possible.
### 🅱️ With `unstable_dev()` API by `wrangler` package
You need to `npm install -D warngler` and create `UnstableDevWorker` instance.
```js
import { unstable_dev } from "wrangler";
import { KVNamespace$ } from "cfw-bindings-wrangler-bridge";const worker = await unstable_dev(
"./path/to/node_modules/cfw-bindings-wrangler-bridge/worker/index.js",
{
local: false,
// config: "./path/to/your/wrangler.toml",
experimental: { disableExperimentalWarning: true },
},
);const MY_KV = new KVNamespace$("MY_KV", {
bridgeWorkerOrigin: `http://${worker.address}:${worker.port}`,
});// ✌️ This is remote KV!
await MY_KV.put("foo", "bar");
await MY_KV.get("foo"); // "bar"// DO NOT FORGET...
await worker.stop();
```This is Node.js only option since `wrangler` package depends on Node.js but may be handy for some cases.
It requires asynchronous style APIs for setup and dev worker should be managed by yourself.
## Advanced usage
Create multiple module instances automatically by `getBindings()` helper.
```ts
import { getBindings } from "cfw-bindings-wrangler-bridge";const env = await getBindings<{
TODOS: KVNamespace;
SESSIONS: KVNamespace;
}>();const user = await env.SESSIONS.get("abc", "json");
const todos = await env.TODOS.get(user.id);
```---
Mixing local and remote service bindings is now possible.
> https://github.com/cloudflare/workers-sdk/issues/1182
```js
import { Fetcher$ } from "cfw-bindings-wrangler-bridge";const AUTH_SERVICE = new Fetcher$("AUTH", {
bridgeWorkerOrigin: "http://127.0.0.1:3000",
});
const CART_SERVICE = new Fetcher$("CART", {
bridgeWorkerOrigin: "http://127.0.0.1:4000",
});
```---
Any type of bindings can be mixed with local and remote.
```js
import { unstable_dev } from "wrangler";
import { R2Bucket$, KVNamespace$ } from "cfw-bindings-wrangler-bridge";const prodWorker = await unstable_dev(/* ... */);
// Use remote
const PROD_ASSETS = new R2Bucket$("ASSETS", {
bridgeWorkerOrigin: `http://${prodWorker.address}:${prodWorker.port}`,
});
// Use local
const DEV_KV = new KVNamespace$("SNAPSHOTS");
```> [!NOTE]
> AFAIK, this is the only way to achieve this situation for now.## Supported bindings
| binding | module | support | memo |
| :----------------------------------------------------------------------------------- | :---------------- | :-----: | :--------------------------------------------- |
| [KV namespace](https://developers.cloudflare.com/workers/runtime-apis/kv/) | `KVNamespace$` | 💯 | |
| [R2 bucket](https://developers.cloudflare.com/r2/api/workers/workers-api-reference/) | `R2Bucket$` | 💯 | |
| [D1 database](https://developers.cloudflare.com/d1/platform/client-api/) | `D1Database$` | 💯 | |
| [Service](https://developers.cloudflare.com/workers/runtime-apis/service-bindings/) | `Fetcher$` | 💯 | |
| [Queue](https://developers.cloudflare.com/queues/platform/javascript-apis/) | `WorkerQueue$` | 💯 | Producer usage only |
| [Vectorize](https://developers.cloudflare.com/vectorize/platform/client-api/) | `VectorizeIndex$` | 💯 | `--remote` only [for now](#vectorize-bindings) |More to come...? PRs are welcome! 😉
## Known limitations
### Compatibility issues
The instances and values available from this bridge are not 100% compatible.
For example,
- Binding instances
- The class constructors like `KVNamespace`, `R2Object`(aka `HeadResult`) are not publicly exposed
- Enumerable instance properties
- Read-only properties are emulated by simple implementation
- Some private properties and methods are included
- Exception
- Not a specific error like `TypeError`, but just an `Error`
- etc...But I don't think there are any problems in practical use.
### Max limits for API call
For example, KV has a limitation of only being able to call the API up to 1000 operations per 1 worker invocation.
However, via this bridge, the API call becomes a separate worker invocation, which circumvents that limitation.
This may be a problem after you deployed that worker.
### Vectorize bindings
Since `wrangler(miniflare)` does not support Vectorize yet, you need `--remote` to interact with Vectorize binding.
> https://github.com/cloudflare/workers-sdk/issues/4360
## Examples
### CLI
If you are using REST API in your CLI, now you can replace it.
```diff
-const putKV = async (API_KEY, API_URL, [key, value]) => {
- const res = await fetch(`${API_URL}/values/${key}`, {
- method: "PUT",
- headers: { Authorization: `Bearer ${API_KEY}` },
- body: value,
- });
-
- const json = await res.json();
- if (!json.success)
- throw new Error(json.errors.map(({ message }) => message).join("\n"));
-};
+import { KVNamespace$ } from "cfw-bindings-wrangler-bridge";
+import { unstable_dev } from "wrangler";
+
+const worker = await unstable_dev(/* ... */);
+
+const putKV = async (KV_BINDING_NAME, [key, value]) => {
+ const KV = new KVNamespace$(
+ KV_BINDING_NAME,
+ { bridgeWorkerOrigin: `http://${worker.address}:${worker.port}` }
+ );
+ await KV.put(key, value);
+};
+
+await worker.stop();
```### SvelteKit local development
Be sure to wrap with `if (dev) {}`, not to be included in production build.
```js
// server.hooks.js
import { KVNamespace$, D1Database$ } from "cfw-bindings-wrangler-bridge";
import { dev } from "$app/environment";export const handle = async ({ event, resolve }) => {
if (dev) {
event.platform = {
env: {
SESSIONS: new KVNamespace$("SESSIONS"),
TODOS: new D1Database$("TODOS"),
},
};
}return resolve(event);
};
```OR
```ts
import { getBindings } from "cfw-bindings-wrangler-bridge";
import { unstable_dev } from "wrangler";
import { dev } from "$app/environment";
import type { Handle } from "@sveltejs/kit";
import type { UnstableDevWorker } from "wrangler";let worker: UnstableDevWorker;
let env: App.Platform["env"];
export const handle: Handle = async ({ event, resolve }) => {
if (dev) {
if (!env) {
worker = await unstable_dev(
"./node_modules/cfw-bindings-wrangler-bridge/worker/index.js",
{ experimental: { disableExperimentalWarning: true } },
);
env = await getBindings({
bridgeWorkerOrigin: `http://${worker.address}:${worker.port}`,
});
}event.platform = { env };
}return resolve(event);
};
```### Astro local development
Be sure to wrap with `if (import.meta.env.DEV) {}`, not to be included in production build.
```astro
---
// your-page.astro
import { getRuntime } from "@astrojs/cloudflare/runtime";
import { KVNamespace$ } from "cfw-bindings-wrangler-bridge";let runtime = getRuntime(Astro.request) ?? {};
if (import.meta.env.DEV) {
runtime.env = {
NEWS: new KVNamespace$("NEWS"),
};
}
---```