Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion src/_drivers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { AzureKeyVaultOptions as AzureKeyVaultOptions } from "unstorage/dri
import type { AzureStorageBlobOptions as AzureStorageBlobOptions } from "unstorage/drivers/azure-storage-blob";
import type { AzureStorageTableOptions as AzureStorageTableOptions } from "unstorage/drivers/azure-storage-table";
import type { CapacitorPreferencesOptions as CapacitorPreferencesOptions } from "unstorage/drivers/capacitor-preferences";
import type { CacheOptions as CloudflareCacheBindingOptions } from "unstorage/drivers/cloudflare-cache-binding";
import type { KVOptions as CloudflareKVBindingOptions } from "unstorage/drivers/cloudflare-kv-binding";
import type { KVHTTPOptions as CloudflareKVHttpOptions } from "unstorage/drivers/cloudflare-kv-http";
import type { CloudflareR2Options as CloudflareR2BindingOptions } from "unstorage/drivers/cloudflare-r2-binding";
Expand All @@ -33,7 +34,7 @@ import type { VercelBlobOptions as VercelBlobOptions } from "unstorage/drivers/v
import type { VercelKVOptions as VercelKVOptions } from "unstorage/drivers/vercel-kv";
import type { VercelCacheOptions as VercelRuntimeCacheOptions } from "unstorage/drivers/vercel-runtime-cache";

export type BuiltinDriverName = "azure-app-configuration" | "azureAppConfiguration" | "azure-cosmos" | "azureCosmos" | "azure-key-vault" | "azureKeyVault" | "azure-storage-blob" | "azureStorageBlob" | "azure-storage-table" | "azureStorageTable" | "capacitor-preferences" | "capacitorPreferences" | "cloudflare-kv-binding" | "cloudflareKVBinding" | "cloudflare-kv-http" | "cloudflareKVHttp" | "cloudflare-r2-binding" | "cloudflareR2Binding" | "db0" | "deno-kv-node" | "denoKVNode" | "deno-kv" | "denoKV" | "fs-lite" | "fsLite" | "fs" | "github" | "http" | "indexedb" | "localstorage" | "lru-cache" | "lruCache" | "memory" | "mongodb" | "netlify-blobs" | "netlifyBlobs" | "null" | "overlay" | "planetscale" | "redis" | "s3" | "session-storage" | "sessionStorage" | "uploadthing" | "upstash" | "vercel-blob" | "vercelBlob" | "vercel-kv" | "vercelKV" | "vercel-runtime-cache" | "vercelRuntimeCache";
export type BuiltinDriverName = "azure-app-configuration" | "azureAppConfiguration" | "azure-cosmos" | "azureCosmos" | "azure-key-vault" | "azureKeyVault" | "azure-storage-blob" | "azureStorageBlob" | "azure-storage-table" | "azureStorageTable" | "capacitor-preferences" | "capacitorPreferences" | "cloudflare-cache-binding" | "cloudflareCacheBinding" | "cloudflare-kv-binding" | "cloudflareKVBinding" | "cloudflare-kv-http" | "cloudflareKVHttp" | "cloudflare-r2-binding" | "cloudflareR2Binding" | "db0" | "deno-kv-node" | "denoKVNode" | "deno-kv" | "denoKV" | "fs-lite" | "fsLite" | "fs" | "github" | "http" | "indexedb" | "localstorage" | "lru-cache" | "lruCache" | "memory" | "mongodb" | "netlify-blobs" | "netlifyBlobs" | "null" | "overlay" | "planetscale" | "redis" | "s3" | "session-storage" | "sessionStorage" | "uploadthing" | "upstash" | "vercel-blob" | "vercelBlob" | "vercel-kv" | "vercelKV" | "vercel-runtime-cache" | "vercelRuntimeCache";

export type BuiltinDriverOptions = {
"azure-app-configuration": AzureAppConfigurationOptions;
Expand All @@ -48,6 +49,8 @@ export type BuiltinDriverOptions = {
"azureStorageTable": AzureStorageTableOptions;
"capacitor-preferences": CapacitorPreferencesOptions;
"capacitorPreferences": CapacitorPreferencesOptions;
"cloudflare-cache-binding": CloudflareCacheBindingOptions;
"cloudflareCacheBinding": CloudflareCacheBindingOptions;
"cloudflare-kv-binding": CloudflareKVBindingOptions;
"cloudflareKVBinding": CloudflareKVBindingOptions;
"cloudflare-kv-http": CloudflareKVHttpOptions;
Expand Down Expand Up @@ -100,6 +103,8 @@ export const builtinDrivers = {
"azureStorageTable": "unstorage/drivers/azure-storage-table",
"capacitor-preferences": "unstorage/drivers/capacitor-preferences",
"capacitorPreferences": "unstorage/drivers/capacitor-preferences",
"cloudflare-cache-binding": "unstorage/drivers/cloudflare-cache-binding",
"cloudflareCacheBinding": "unstorage/drivers/cloudflare-cache-binding",
"cloudflare-kv-binding": "unstorage/drivers/cloudflare-kv-binding",
"cloudflareKVBinding": "unstorage/drivers/cloudflare-kv-binding",
"cloudflare-kv-http": "unstorage/drivers/cloudflare-kv-http",
Expand Down
112 changes: 112 additions & 0 deletions src/drivers/cloudflare-cache-binding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import type {
Cache as CFCache,
CacheStorage as CFCacheStorage,
Response as CFResponse,
} from "@cloudflare/workers-types/experimental";

import { defineDriver, joinKeys } from "./utils";

export interface CacheOptions {
/**
* Optional prefix to use for all keys. Can be used for namespacing.
*/
base?: string;

/**
* Default TTL for all items in seconds.
*/
ttl?: number;

/**
* Name of the cache to use.
* The default is `caches.default`, otherwise `caches.open(cacheName)` is used.
* In Workers for Platforms, `caches.default` is disabled for namespaced scripts, so a cache name must be provided. See Cloudflare's [documentation](https://developers.cloudflare.com/cloudflare-for-platforms/workers-for-platforms/reference/how-workers-for-platforms-works/#cache-api).
*/
name?: string;
}

// https://developers.cloudflare.com/workers/runtime-apis/cache

const DRIVER_NAME = "cloudflare-cache-binding";

export default defineDriver((opts: CacheOptions) => {
const r = (key: string = "") => {
if (opts.base) {
key = joinKeys(opts.base, key);
}
return `unstorage://${key.replace(/:/g, "/")}`;
};

let _cache: CFCache | Promise<CFCache> | undefined;
const getCache = () => {
if (_cache) {
return _cache;
}
if (opts.name) {
_cache = (globalThis.caches as unknown as CFCacheStorage).open(opts.name);
} else {
_cache = (globalThis.caches as unknown as CFCacheStorage).default;
}
return _cache;
};

return {
name: DRIVER_NAME,
options: opts,
getInstance: () => getCache(),

async hasItem(key) {
const cacheKey = r(key);
const cache = await getCache();
const match = await cache.match(cacheKey);
return match !== undefined;
},

async getItem(key) {
const cacheKey = r(key);
const cache = await getCache();
const response = await cache.match(cacheKey);
return response ? await response.text() : null;
},

async getItemRaw(key) {
const cacheKey = r(key);
const cache = await getCache();
const response = await cache.match(cacheKey);
return response ? await response.arrayBuffer() : null;
},

async setItem(key, value, tOptions) {
return this.setItemRaw!(key, value, tOptions);
},

async setItemRaw(key, value, tOptions) {
const cacheKey = r(key);

// https://developers.cloudflare.com/workers/runtime-apis/cache/#headers
const headers = {} as Record<string, string>;
const ttl = tOptions?.ttl ?? opts.ttl;
if (ttl) {
headers["Cache-Control"] = `max-age=${ttl}`;
}
if (tOptions.tag) {
headers["Cache-Tag"] = tOptions.tag;
}

const cacheValue = new Response(value, { headers });

const cache = await getCache();
await cache.put(cacheKey, cacheValue as unknown as CFResponse);
},

async removeItem(key) {
const cacheKey = r(key);
const cache = await getCache();
await cache.delete(cacheKey);
},

getKeys() {
return [];
},
};
});
40 changes: 40 additions & 0 deletions test/drivers/cloudflare-cache-binding.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/// <reference types="@cloudflare/workers-types" />
import { describe, expect, test, afterAll } from "vitest";
import CloudflareCacheBinding from "../../src/drivers/cloudflare-cache-binding";
import { testDriver } from "./utils";
import { getPlatformProxy } from "wrangler";

/*
Wrangler platform proxy does seems has no real implementation of caches API so implementation had been manually tested

export default {
async fetch(request, env, ctx) {
const cache = caches.default
await cache.put("unstorage://test/key", new Response("foobar"))
const value = await cache.match("unstorage://test/key")
return Response.json({
cacheValue: await value.text()
})
},
};
*/

describe.skip("drivers: cloudflare-cache-binding", async () => {
const cfProxy = await getPlatformProxy();
(globalThis as any).caches = cfProxy.caches;
afterAll(async () => {
(globalThis as any).caches = undefined;
await cfProxy.dispose();
});
testDriver({
driver: CloudflareCacheBinding({ base: "base" }),
async additionalTests(ctx) {
test.only("basic", async () => {
console.log(cfProxy.caches.default.match.toString());
await ctx.driver!.setItem!("key", "value", {});
const value = await ctx.driver!.getItem!("key");
expect(value).toBe("value");
});
},
});
});