Skip to content

Commit

Permalink
feat(cloudflare): add local mocking for KV bindings (#8657)
Browse files Browse the repository at this point in the history
* feat(cloudflare): Add KV Bindings

---------

Co-authored-by: Sarah Rainsberger <[email protected]>

* feat(cloudflare): add local mocking for Caches bindings (#8664)

* feature(cloudflare): add Caches bindings

---------

Co-authored-by: Sarah Rainsberger <[email protected]>

* feat(cloudflare): add local mocking for DO bindings (#8690)

---------

Co-authored-by: Sarah Rainsberger <[email protected]>

---------

Co-authored-by: Sarah Rainsberger <[email protected]>

---------

Co-authored-by: Sarah Rainsberger <[email protected]>
  • Loading branch information
alexanderniebuhr and sarah11918 authored Oct 2, 2023
1 parent e8eb3f6 commit a4805f2
Show file tree
Hide file tree
Showing 12 changed files with 166 additions and 27 deletions.
5 changes: 5 additions & 0 deletions .changeset/funny-cobras-accept.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---

Introduces support for local KV bindings. Enhances development experience by allowing direct integration with `astro dev`.
5 changes: 5 additions & 0 deletions .changeset/hot-readers-live.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---

Introduces support for local Durable Objects bindings. Enhances development experience by allowing direct integration with `astro dev`.
5 changes: 5 additions & 0 deletions .changeset/yellow-kiwis-pretend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@astrojs/cloudflare': minor
---

Introduces support for local Caches bindings. Enhances development experience by allowing direct integration with `astro dev`.
2 changes: 2 additions & 0 deletions packages/integrations/cloudflare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,8 @@ Currently supported bindings:

- [Cloudflare D1](https://developers.cloudflare.com/d1/)
- [Cloudflare R2](https://developers.cloudflare.com/r2/)
- [Cloudflare Workers KV](https://developers.cloudflare.com/kv/)
- [Cloudflare Durable Objects](https://developers.cloudflare.com/durable-objects/)

You can access the runtime from Astro components through `Astro.locals` inside any .astro` file.

Expand Down
48 changes: 26 additions & 22 deletions packages/integrations/cloudflare/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import type { AstroConfig, AstroIntegration, RouteData } from 'astro';

import { createRedirectsFromAstroRoutes } from '@astrojs/underscore-redirects';
import { CacheStorage } from '@miniflare/cache';
import { NoOpLog } from '@miniflare/shared';
import { MemoryStorage } from '@miniflare/storage-memory';
import { AstroError } from 'astro/errors';
import esbuild from 'esbuild';
import { Miniflare } from 'miniflare';
Expand All @@ -15,7 +12,13 @@ import glob from 'tiny-glob';
import { getAdapter } from './getAdapter.js';
import { deduplicatePatterns } from './utils/deduplicatePatterns.js';
import { getCFObject } from './utils/getCFObject.js';
import { getD1Bindings, getEnvVars, getR2Bindings } from './utils/parser.js';
import {
getD1Bindings,
getDOBindings,
getEnvVars,
getKVBindings,
getR2Bindings,
} from './utils/parser.js';
import { prependForwardSlash } from './utils/prependForwardSlash.js';
import { rewriteWasmImportPath } from './utils/rewriteWasmImportPath.js';
import { wasmModuleLoader } from './utils/wasm-module-loader.js';
Expand Down Expand Up @@ -56,17 +59,6 @@ interface BuildConfig {
split?: boolean;
}

class StorageFactory {
storages = new Map();

storage(namespace: string) {
let storage = this.storages.get(namespace);
if (storage) return storage;
this.storages.set(namespace, (storage = new MemoryStorage()));
return storage;
}
}

export default function createIntegration(args?: Options): AstroIntegration {
let _config: AstroConfig;
let _buildConfig: BuildConfig;
Expand Down Expand Up @@ -126,7 +118,8 @@ export default function createIntegration(args?: Options): AstroIntegration {
const vars = await getEnvVars();
const D1Bindings = await getD1Bindings();
const R2Bindings = await getR2Bindings();

const KVBindings = await getKVBindings();
const DOBindings = await getDOBindings();
let bindingsEnv = new Object({});

// fix for the error "kj/filesystem-disk-unix.c++:1709: warning: PWD environment variable doesn't match current directory."
Expand All @@ -139,10 +132,15 @@ export default function createIntegration(args?: Options): AstroIntegration {
script: '',
cache: true,
cachePersist: true,
cacheWarnUsage: true,
d1Databases: D1Bindings,
d1Persist: true,
r2Buckets: R2Bindings,
r2Persist: true,
kvNamespaces: KVBindings,
kvPersist: true,
durableObjects: DOBindings,
durableObjectsPersist: true,
});
await _mf.ready;

Expand All @@ -154,6 +152,17 @@ export default function createIntegration(args?: Options): AstroIntegration {
const bucket = await _mf.getR2Bucket(R2Binding);
Reflect.set(bindingsEnv, R2Binding, bucket);
}
for (const KVBinding of KVBindings) {
const namespace = await _mf.getKVNamespace(KVBinding);
Reflect.set(bindingsEnv, KVBinding, namespace);
}
for (const key in DOBindings) {
if (Object.prototype.hasOwnProperty.call(DOBindings, key)) {
const DO = await _mf.getDurableObjectNamespace(key);
Reflect.set(bindingsEnv, key, DO);
}
}
const mfCache = await _mf.getCaches();

process.env.PWD = originalPWD;
const clientLocalsSymbol = Symbol.for('astro.locals');
Expand All @@ -176,12 +185,7 @@ export default function createIntegration(args?: Options): AstroIntegration {
waitUntil: (_promise: Promise<any>) => {
return;
},
caches: new CacheStorage(
{ cache: true, cachePersist: false },
new NoOpLog(),
new StorageFactory(),
{}
),
caches: mfCache,
},
});
next();
Expand Down
27 changes: 27 additions & 0 deletions packages/integrations/cloudflare/src/utils/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,30 @@ export async function getR2Bindings() {
);
return bindings;
}

export async function getKVBindings() {
const { rawConfig } = parseConfig();
if (!rawConfig) return [];
if (!rawConfig?.kv_namespaces) return [];
const bindings = (rawConfig?.kv_namespaces as []).map(
(binding: { binding: string }) => binding.binding
);
return bindings;
}

export function getDOBindings(): Record<
string,
{ scriptName?: string | undefined; unsafeUniqueKey?: string | undefined; className: string }
> {
const { rawConfig } = parseConfig();
if (!rawConfig) return {};
if (!rawConfig?.durable_objects) return {};
const output = new Object({}) as Record<
string,
{ scriptName?: string | undefined; unsafeUniqueKey?: string | undefined; className: string }
>;
for (const binding of rawConfig?.durable_objects.bindings) {
Reflect.set(output, binding.name, { className: binding.class_name });
}
return output;
}
35 changes: 32 additions & 3 deletions packages/integrations/cloudflare/test/cf.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,20 @@ describe('Astro Cloudflare Runtime', () => {
await devServer?.stop();
});

it('Populates CF, Vars & Bindings', async () => {
it('adds cf object', async () => {
let res = await fixture.fetch('/');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasRuntime').text()).to.equal('true');
expect($('#hasCache').text()).to.equal('true');
expect($('#hasCF').text()).to.equal('true');
});

it('adds cache mocking', async () => {
let res = await fixture.fetch('/caches');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasCACHE').text()).to.equal('true');
});

it('adds D1 mocking', async () => {
Expand All @@ -95,4 +102,26 @@ describe('Astro Cloudflare Runtime', () => {
expect($('#hasPRODBUCKET').text()).to.equal('true');
expect($('#hasACCESS').text()).to.equal('true');
});

it('adds KV mocking', async () => {
expect(await fixture.pathExists('../.mf/kv')).to.be.true;

let res = await fixture.fetch('/kv');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasKV').text()).to.equal('true');
expect($('#hasPRODKV').text()).to.equal('true');
expect($('#hasACCESS').text()).to.equal('true');
});

it('adds DO mocking', async () => {
expect(await fixture.pathExists('../.mf/do')).to.be.true;

let res = await fixture.fetch('/do');
expect(res.status).to.equal(200);
let html = await res.text();
let $ = cheerio.load(html);
expect($('#hasDO').text()).to.equal('true');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
const runtime = Astro.locals.runtime;
---

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CACHES</title>
</head>
<body>
<pre id="hasCACHE">{!!runtime.caches}</pre>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
const runtime = Astro.locals.runtime;
---

<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>DO</title>
</head>
<body>
<pre id="hasDO">{!!runtime.env.DO}</pre>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const runtime = Astro.locals.runtime;
</head>
<body>
<h1>Testing</h1>
<div id="hasRuntime">{!!runtime.cf?.colo}</div>
<div id="hasCache">{!!runtime.caches}</div>
<div id="hasCF">{!!runtime.cf?.colo}</div>
</body>
</html>
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
const runtime = Astro.locals.runtime;
const kv = runtime.env?.KV;
await kv.put("test", "true");
const result = await kv.get("test")
---

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>KV</title>
</head>
<body>
<pre id="hasKV">{!!runtime.env?.KV}</pre>
<pre id="hasPRODKV">{!!runtime.env?.KV_PROD}</pre>
<pre id="hasACCESS">{!!result}</pre>
</body>
</html>
13 changes: 13 additions & 0 deletions packages/integrations/cloudflare/test/fixtures/cf/wrangler.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
name = "test"

kv_namespaces = [
{ binding = "KV", id = "<YOUR_ID>", preview_id = "<YOUR_ID>" },
{ binding = "KV_PROD", id = "<YOUR_ID>", preview_id = "<YOUR_ID>" }
]

[vars]
COOL = "ME"

Expand All @@ -22,3 +27,11 @@ bucket_name = '<YOUR_BUCKET_NAME>'
[[r2_buckets]]
binding = 'R2_PROD' # <~ valid JavaScript variable name
bucket_name = '<YOUR_BUCKET_NAME>'

[[durable_objects.bindings]]
name = "DO"
class_name = "DurableObjectExample"

[[durable_objects.bindings]]
name = "DO_PROD"
class_name = "DurableObjectProductionExample"

0 comments on commit a4805f2

Please sign in to comment.