Skip to content

Commit e15e190

Browse files
committed
feat: add shamefullyPatchR2Buckets
1 parent 76062e3 commit e15e190

8 files changed

Lines changed: 91 additions & 5 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ You can configure additional options using `cloudflareDev: { }` in `nitro.config
4444
- `persistDir`: Sets the persist dir (default `.wrangler/state/v3`).
4545
- `configPath`: Sets a custom path for `wrangler.toml` file.
4646
- `silent`: Hide initial banner.
47+
- `shamefullyPatchR2Buckets`: Add workaround for https://github.com/cloudflare/workers-sdk/issues/5360
4748

4849
## Development
4950

examples/nitro/nitro.config.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@ import nitroCloudflareBindings from "nitro-cloudflare-dev";
33
// https://nitro.unjs.io/config
44
export default defineNitroConfig({
55
modules: [nitroCloudflareBindings],
6+
cloudflareDev: {
7+
shamefullyPatchR2Buckets: true,
8+
},
69
});

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"pkg-types": "^1.0.3"
4242
},
4343
"devDependencies": {
44+
"@cloudflare/workers-types": "^4.20240320.1",
4445
"@types/node": "^20.11.30",
4546
"changelogen": "^0.5.5",
4647
"eslint": "^8.57.0",

pnpm-lock.yaml

Lines changed: 10 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ declare module "nitropack" {
1414
cloudflareDev?: {
1515
configPath?: string;
1616
persistDir?: string;
17-
silent: boolean;
17+
silent?: boolean;
18+
/** workaround for https://github.com/cloudflare/workers-sdk/issues/5360 */
19+
shamefullyPatchR2Buckets?: boolean;
1820
};
1921
}
2022
}
@@ -68,6 +70,8 @@ async function nitroModule(nitro: Nitro) {
6870
// Share config to the runtime
6971
nitro.options.runtimeConfig.wrangler = {
7072
...nitro.options.runtimeConfig.wrangler,
73+
shamefullyPatchR2Buckets:
74+
nitro.options.cloudflareDev?.shamefullyPatchR2Buckets,
7175
configPath,
7276
persistDir,
7377
};

src/runtime/plugin.dev.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,21 +64,35 @@ async function _getPlatformProxy() {
6464
})) as typeof import("wrangler");
6565

6666
const runtimeConfig: {
67-
wrangler: { configPath: string; persistDir: string };
67+
wrangler: {
68+
configPath: string;
69+
persistDir: string;
70+
shamefullyPatchR2Buckets?: boolean;
71+
};
6872
} = useRuntimeConfig();
6973

7074
const proxy = await getPlatformProxy({
7175
configPath: runtimeConfig.wrangler.configPath,
7276
persist: { path: runtimeConfig.wrangler.persistDir },
7377
});
7478

79+
if (runtimeConfig.wrangler.shamefullyPatchR2Buckets) {
80+
const { patchR2Bucket } = await import("./r2-patch");
81+
for (const [key, binding] of Object.entries(proxy.env) as [string, any][]) {
82+
if (binding.createMultipartUpload) {
83+
// console.log("Patching R2Bucket", key);
84+
proxy.env[key] = patchR2Bucket(binding);
85+
}
86+
}
87+
}
88+
7589
return proxy;
7690
}
7791

7892
function _createStubProxy(): PlatformProxy {
7993
return {
8094
env: {},
81-
cf: {},
95+
cf: {} as any,
8296
ctx: {
8397
waitUntil() {},
8498
passThroughOnException() {},

src/runtime/r2-patch.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { R2Bucket } from "@cloudflare/workers-types";
2+
/**
3+
* Workaround for https://github.com/cloudflare/workers-sdk/issues/5360
4+
*/
5+
export function patchR2Bucket(bucket: R2Bucket) {
6+
let _mutex: Promise<any> | undefined;
7+
8+
const _get = bucket.get.bind(bucket);
9+
10+
async function getAndRead(...args: Parameters<R2Bucket["get"]>) {
11+
const obj = await _get(...args);
12+
if (!obj) {
13+
return obj;
14+
}
15+
const chunks: any[] = [];
16+
for await (const chunk of obj.body) {
17+
chunks.push(chunk);
18+
}
19+
const body = new ReadableStream({
20+
start(controller) {
21+
for (const chunk of chunks) {
22+
controller.enqueue(chunk);
23+
}
24+
controller.close();
25+
},
26+
});
27+
return { ...obj, body };
28+
}
29+
30+
async function get(...args: Parameters<R2Bucket["get"]>) {
31+
if (_mutex) {
32+
await _mutex;
33+
}
34+
try {
35+
_mutex = getAndRead(...args);
36+
const obj = await _mutex;
37+
return obj;
38+
} finally {
39+
_mutex = undefined;
40+
}
41+
}
42+
43+
return new Proxy(bucket, {
44+
get(target, prop) {
45+
if (prop === "get") {
46+
return get;
47+
}
48+
return Reflect.get(target, prop);
49+
},
50+
});
51+
}

wrangler.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ compatibility_date = "2022-07-12"
33
kv_namespaces = [
44
{ binding = "KV", id = "<KV_ID>" }
55
]
6+
7+
r2_buckets = [
8+
{ binding = "BLOB", bucket_name = "default" },
9+
]

0 commit comments

Comments
 (0)