Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement importable env #3610

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open

Implement importable env #3610

wants to merge 7 commits into from

Conversation

jasnell
Copy link
Member

@jasnell jasnell commented Feb 26, 2025

PR marked as a draft until we're certain this is what we want....

import { env } from 'cloudflare:workers';

console.log(env.FOO);  // Undefined, outside request io context

export default {
  fetch(req) {
    console.log(env.FOO);  // 'BAR', inside request io context
    // ...
  }
}

Gated by a compat flag that is off by default The flag is now on by default.

  • allow_importable_env
  • disallow_importable_env

Mutations of the env properties are passed through to the underlying object so will be reflected on the env argument passed into the handlers.

Note that this is not related to process.env... this exposes the regular env/bindings via the import when accessed within the context of a request. As such, any modules used by your worker can access the bindings without having them explicitly passed down.

@jasnell
Copy link
Member Author

jasnell commented Feb 27, 2025

Summary of internal discussion:

  1. We want to go ahead and make env also populated at the top-level scope (PR updated to do so)
  2. It has been suggested that we need a way of propagating a modified/custom context only down specific async contexts (PR updated to do so using withEnv(...)). This uses async context propagation under the covers.
  3. Test case has been updated to illustrate the updated functionality.

@jasnell jasnell force-pushed the jasnell/importable-env branch from 8d7dffe to 66d9c5c Compare February 27, 2025 00:35
@jasnell
Copy link
Member Author

jasnell commented Feb 27, 2025

Next round of updates:

  1. Reversed the polarity of the compat flag. The importable env is now populated/enabled by default with a compat flag to switch it off
  2. withEnv(...) will work even if the disable flag is turned on.

Next known remaining todos:

  1. Simplify the instrumentation to avoid having to instrument multiple times. Implement importable env #3610 (comment) Done

@jasnell jasnell force-pushed the jasnell/importable-env branch from 36e3c6f to 9a88837 Compare February 27, 2025 18:02
@jasnell jasnell marked this pull request as ready for review February 27, 2025 19:34
@jasnell jasnell requested review from a team as code owners February 27, 2025 19:34
@jasnell jasnell requested a review from erikcorry February 27, 2025 19:34
@jasnell
Copy link
Member Author

jasnell commented Feb 27, 2025

Hmm, yeah, damn you're right :-/ ... ok, it's going to get rather complicated then. The other alternative I was considering is adding a new Worker::current() backed by a thread-local that would allow me to Worker::current().getEnv(js) ... but I'm a bit leery about adding a new thread local. The other approach I considered was adding a callback that can be set such that js.getWorkerEnv() calls back to the worker to grab the env, but I like this less than the thread local. What do you think @kentonv

@kentonv
Copy link
Member

kentonv commented Feb 27, 2025

See my last comment above:

Note that we don't need imported env to support dynamic bindings. For people using dynamic bindings, we can just say: don't use imported env. So I think imported env can just reflect the first env ever received. But the env delivered to event handlers as a parameter still needs to be the per-request env, which we still need to store in Worker::Impl.

So I think you can keep the implementation as-is, except don't remove Worker::Impl::env... there's just two places where we track env, oh well.

@jasnell jasnell requested a review from kentonv February 27, 2025 21:01
],
compatibilityDate = "2025-02-01",
compatibilityFlags = [
"nodejs_compat_v2",
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this need nodejs compat?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh... Assert

Copy link
Member Author

Choose a reason for hiding this comment

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

I've been debating whether to make node:assert always available in wd-tests.

@vicb
Copy link
Contributor

vicb commented Feb 27, 2025

The PR description mentions that the flag is off by default. I think this is not the current state.

Would be nice to read about security considerations in the description too. IIUC this could be used by any library to get access to the bindings.

@vicb
Copy link
Contributor

vicb commented Feb 27, 2025

One last thing: is "(dis)allow_importable_env" really reflecting what this does?

IIUC the PR does not (dis)allow anything. Env is always importable - sometimes not populated/connected.

Would be nice to have a better description on the PR description and maybe a name reflecting the actual behavior.

Thanks

@jasnell
Copy link
Member Author

jasnell commented Feb 27, 2025

Happy to accept suggestions for alternative names for the compat flags. I feel they are descriptive of what they are doing but opinions may vary

```js
import { env } from 'cloudflare:workers';

console.log(env.FOO);  // Undefined, outside request io context

export default {
  fetch(req) {
    console.log(env.FOO);  // 'BAR', inside request io context
    // ...
  }
}
```

Gated by a compat flag that is off by default

  * `allow_importable_env`
  * `disallow_importable_env`

Mutations of the env properties are passed through to the underlying
object so will be reflected on the `env` argument passed into the
handlers.
Rather than persisting the env in Worker::Impl, store it in
jsg::Lock so that it is easier to access from places that do
not have access to Worker::Impl.
@jasnell jasnell force-pushed the jasnell/importable-env branch from f07505e to 2603d7e Compare February 27, 2025 23:16
@vicb
Copy link
Contributor

vicb commented Feb 28, 2025

"Note that this is not related to process.env..."

It actually is somehow related now that process.env is populated from the env.
IMO it's important to document that mutating an importable env will not affect process.env (and probably vice-versa while we're are at it)


import { strictEqual, notDeepStrictEqual } from 'node:assert';

strictEqual(env.FOO, undefined);
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe add a test setting env.BAR not reflected on argEnv.BAR

Comment on lines +734 to +735
# When enabled, `import { env } from 'cloudflare:workers'` will provide access
# the per-request environment/bindings.
Copy link
Contributor

@vicb vicb Feb 28, 2025

Choose a reason for hiding this comment

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

Suggested change
# When enabled, `import { env } from 'cloudflare:workers'` will provide access
# the per-request environment/bindings.
# When allowed, `import { env } from 'cloudflare:workers'` will provide access
# to the per-request environment/bindings.

if (inner) {
return Reflect.deleteProperty(inner, prop);
}
return true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this return false here ?

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

Successfully merging this pull request may close these issues.

3 participants