cfw-bindings-wrangler-bridge
Version:
This bridge makes it possible to interact with **remote** Cloudflare Workers bindings(like KV, D1, etc...) **from anywhere** you want.
126 lines (105 loc) • 3.74 kB
JavaScript
// @ts-check
// Refs:
// https://developers.cloudflare.com/workers/runtime-apis/kv/
// https://github.com/cloudflare/workerd/blob/main/src/workerd/api/kv.c%2B%2B#L230
// https://github.com/cloudflare/miniflare/blob/tre/packages/miniflare/src/plugins/kv/gateway.ts#L155
import { stringify, parse } from "devalue";
import { resolveModuleOptions } from "../options.js";
/** @typedef {import("../index.d.ts").BridgeModuleOptions} BridgeModuleOptions */
/** @param {string} key */
const encodeKey = (key) => encodeURIComponent(key);
export class KVNamespace$ {
#bindingName;
#bridgeWorkerOrigin;
/**
* @param {string} bindingName
* @param {BridgeModuleOptions} [options]
*/
constructor(bindingName, options) {
this.#bindingName = bindingName;
const { bridgeWorkerOrigin } = resolveModuleOptions(options);
this.#bridgeWorkerOrigin = bridgeWorkerOrigin;
}
/**
* @param {string} operation
* @param {unknown[]} parameters
* @param {BodyInit} [body]
*/
async #dispatch(operation, parameters, body) {
const res = await fetch(this.#bridgeWorkerOrigin, {
method: "POST",
headers: {
"X-BRIDGE-BINDING-MODULE": "KV",
"X-BRIDGE-BINDING-NAME": this.#bindingName,
"X-BRIDGE-KV-Dispatch": stringify({ operation, parameters }),
},
body,
// @ts-expect-error: https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1483
duplex: "half",
});
if (!res.ok) {
const error = await res.text();
throw new Error(error);
}
return res;
}
/** @param {import("@cloudflare/workers-types/experimental").KVNamespaceListOptions} [options] */
async list(options) {
const res = await this.#dispatch("KVNamespace.list", [options]);
const json = await res.json();
return json;
}
/**
* @param {string} key
* @param {ReadableStream | ArrayBuffer | ArrayBufferView | string} value
* @param {import("@cloudflare/workers-types/experimental").KVNamespacePutOptions} [options]
*/
async put(key, value, options) {
await this.#dispatch("KVNamespace.put", [encodeKey(key), options], value);
}
/**
* @template Type
* @param {string} key
* @param {import("@cloudflare/workers-types/experimental").KVNamespaceGetOptions<Type>} [typeOrOptions]
*/
async get(key, typeOrOptions) {
const { value } = await this.getWithMetadata(key, typeOrOptions);
return value;
}
/**
* @template Type
* @param {string} key
* @param {import("@cloudflare/workers-types/experimental").KVNamespaceGetOptions<Type>} [typeOrOptions]
*/
async getWithMetadata(key, typeOrOptions) {
const res = await this.#dispatch("KVNamespace.getWithMetadata", [
encodeKey(key),
typeOrOptions,
]);
if (res.headers.get("X-BRIDGE-KV-ValueIsNull") === "true")
return { value: null, metadata: null, cacheStatus: null };
let type;
if (typeof typeOrOptions?.type === "string") {
type = typeOrOptions.type;
} else if (typeof typeOrOptions === "string") {
type = typeOrOptions;
} else {
type = "text";
}
let value;
if (type === "json") value = await res.json();
if (type === "text") value = await res.text();
if (type === "arrayBuffer") value = await res.arrayBuffer();
if (type === "stream") value = res.body;
// `null` is `[null]` in `devalue`
const metadata = parse(res.headers.get("X-BRIDGE-KV-Metadata") ?? "[null]");
const cacheStatus = parse(
res.headers.get("X-BRIDGE-KV-CacheStatus") ?? "[null]",
);
return { value, metadata, cacheStatus };
}
/** @param {string} key */
async delete(key) {
await this.#dispatch("KVNamespace.delete", [encodeKey(key)]);
}
}