Skip to content

Conversation

@john-royal
Copy link
Collaborator

@john-royal john-royal commented Dec 11, 2025

Provides access to runtime APIs for KV, D1, R2, and Queue resources. Uses a proxy to lazily initialize a Miniflare instance with either the remote binding or the local one.

Not sure if "node binding" is the right way to describe this - let me know if you have any other ideas.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 11, 2025

Open in StackBlitz

npm i https://pkg.pr.new/alchemy-run/alchemy@1255

commit: 0275e1a

@github-actions
Copy link

github-actions bot commented Dec 11, 2025

🚀 Website Preview Deployed

Your website preview is ready!

Preview URL: https://c0ef23f4-alchemy-website.alchemy-run.workers.dev

This preview was built from commit de7760f


🤖 This comment will be updated automatically when you push new commits to this PR.

@john-royal john-royal marked this pull request as ready for review December 11, 2025 20:48
Comment on lines 468 to 477
resumeMultipartUpload: (promise) => (key: string, uploadId: string) =>
makeAsyncProxy(
{ key, uploadId },
promise.then((bucket) => bucket.resumeMultipartUpload(key, uploadId)),
{
uploadPart: true,
abort: true,
complete: true,
},
),
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

resumeMultipartUpload does not return a promise. To match the type of the runtime API, we make this synchronous by returning another proxy.

This is also done for D1 prepared statements and sessions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you show me an example of what the proxy fixes? Are all the properties Promise<T> or functions returning Promises?

Comment on lines +454 to +455
// TODO(john): this is a problem with @cloudflare/workers-types, it should not be nullable unless options.onlyIf is used
let putObj = (await bucket.put(testKey, testContent)) as R2Object;
Copy link
Collaborator Author

@john-royal john-royal Dec 11, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't merge this PR until I report this to the CF team - this is a weird one

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not merge it? Is it unusable?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's usable, just need to report it or figure out what's going on

@john-royal john-royal linked an issue Dec 11, 2025 that may be closed by this pull request
@sam-goodwin
Copy link
Collaborator

Node bindings doesn't make sense to me. These are just APIs

Comment on lines 303 to 321
withSession: (promise) => (constraintOrBookmark) =>
makeAsyncProxy(
{},
promise.then((database) =>
database.withSession(constraintOrBookmark),
),
{
prepare: (session) => (query) =>
makePreparedStatementProxy(
session.then((session) => session.prepare(query)),
),
batch: true,
getBookmark: () => () => {
throw new Error(
"D1DatabaseSession.getBookmark is not implemented",
);
},
},
),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We will need to add some tests for each of these APIs on all the resources given how much custom logic there is. Please add tests

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right... there are bugs that the test is revealing. This might take a little longer 😅

@shellscape
Copy link

Really looking forward to this one. We've got a fairly decent amount of typing shenanigans to make Alchemy binding types play nice with @cloudflare/workers-types.

@shellscape
Copy link

Just gave this one a shot locally. When using import { R2Bucket } from '@cloudflare/workers-types and trying to assign a bucket binding to an R2Bucket typed variable, we get the following error:

error TS2322: Type 'R2Bucket' is not assignable to type 'import("/Users/code/node_modules/.bun/@cloudflare+workers-types@4.20251212.0/node_modules/@cloudflare/workers-types/index").R2Bucket'.
  The types returned by 'head(...)' are incompatible between these types.
    Type 'Promise<R2Object | null>' is not assignable to type 'Promise<import("/Users/code/node_modules/.bun/@cloudflare+workers-types@4.20251212.0/node_modules/@cloudflare/workers-types/index").R2Object | null>'.
      Type 'R2Object | null' is not assignable to type 'import("/Users/code/node_modules/.bun/@cloudflare+workers-types@4.20251212.0/node_modules/@cloudflare/workers-types/index").R2Object | null'.
        Type 'R2Object' is not assignable to type 'import("/Users/code/node_modules/.bun/@cloudflare+workers-types@4.20251212.0/node_modules/@cloudflare/workers-types/index").R2Object'.
          Types of property 'writeHttpMetadata' are incompatible.
            Type '(headers: Headers) => void' is not assignable to type '(headers: import("/Users/code/node_modules/.bun/@cloudflare+workers-types@4.20251212.0/node_modules/@cloudflare/workers-types/index").Headers) => void'.
              Types of parameters 'headers' and 'headers' are incompatible.
                Property 'getSetCookie' is missing in type 'import("/Users/code/node_modules/.bun/@cloudflare+workers-types@4.20251212.0/node_modules/@cloudflare/workers-types/index").Headers' but required in type 'Headers'.

Fwiw our tsconfig is setup for strict.

Comment on lines +479 to +481
console.log("8. list objects");
const listObj = await bucket.list();
console.log("9. list objects", listObj);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found that the scope was finalizing itself around here, which causes this error to be thrown:

Error: Attempted to use poisoned stub. Stubs to runtime objects must be re-created after calling `Miniflare#setOptions()` or `Miniflare#dispose()`.
 ❯ ProxyStubHandler.#assertSafe node_modules/.bun/miniflare@4.20251011.1+a744fd195f22f642/node_modules/miniflare/src/plugins/core/proxy/client.ts:370:10
 ❯ ProxyStubHandler.get node_modules/.bun/miniflare@4.20251011.1+a744fd195f22f642/node_modules/miniflare/src/plugins/core/proxy/client.ts:4

Tempted to abandon ship on this PR... too many footguns

try {
let database = await D1Database(id);
expect(database.id).toBeTruthy();
const session = database.withSession("first-primary");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add tests that also use .exec and .prepare? These tests only work on sessions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Alchemy R2Bucket types vs @cloudflare/workers-types

4 participants