Skip to content

Commit

Permalink
Implement withEnv to propagate modified workers env
Browse files Browse the repository at this point in the history
  • Loading branch information
jasnell committed Feb 27, 2025
1 parent 11cfe5d commit 66d9c5c
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 4 deletions.
1 change: 1 addition & 0 deletions src/cloudflare/internal/env.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
// Get the current environment, if any
export function getCurrent(): object | undefined;
export function withEnv(newEnv: any, fn: () => any): any;
5 changes: 5 additions & 0 deletions src/cloudflare/workers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export const WorkflowEntrypoint = entrypoints.WorkflowEntrypoint;
// is intentional.
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */

export function withEnv(newEnv: any, fn: () => any): any {
return innerEnv.withEnv(newEnv, fn);
}

// A proxy for the workers env/bindings. The proxy is io-context
// aware in that it will only return values when there is an active
// IoContext. Mutations to the env via this proxy propagate to the
Expand Down
7 changes: 7 additions & 0 deletions src/workerd/api/modules.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,15 @@ class EnvModule final: public jsg::Object {
return kj::none;
}

jsg::JsValue withEnv(jsg::Lock& js, jsg::Value newEnv, jsg::Function<jsg::JsValue()> fn) {
auto& key = jsg::IsolateBase::from(js.v8Isolate).getEnvAsyncContextKey();
jsg::AsyncContextFrame::StorageScope storage(js, key, kj::mv(newEnv));
return fn(js);
}

JSG_RESOURCE_TYPE(EnvModule) {
JSG_METHOD(getCurrent);
JSG_METHOD(withEnv);
}
};

Expand Down
41 changes: 38 additions & 3 deletions src/workerd/api/tests/importable-env-test.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
import { strictEqual, deepStrictEqual, notStrictEqual } from 'node:assert';
import { env } from 'cloudflare:workers';
import {
strictEqual,
deepStrictEqual,
notStrictEqual,
ok,
rejects,
} from 'node:assert';
import { env, withEnv } from 'cloudflare:workers';

// The env is populated at the top level scope.
strictEqual(env.FOO, 'BAR');

// Cache exists and is accessible.
ok(env.CACHE);
// But fails when used because we're not in an io-context
await rejects(
env.CACHE.read('hello', async () => {}),
{
message: /Disallowed operation called within global scope./,
}
);

export const importableEnv = {
async test(_, argEnv) {
// Accessing the cache initially at the global scope didn't break anything
const cached = await argEnv.CACHE.read('hello', async () => {
return {
value: 123,
expiration: Date.now() + 10000,
};
});
strictEqual(cached, 123);

// They aren't the same objects...
notStrictEqual(env, argEnv);
// But have all the same stuff...
Expand All @@ -23,7 +48,17 @@ export const importableEnv = {
const { env: otherEnv } = await import('child');
strictEqual(otherEnv.FOO, 'BAR');
strictEqual(otherEnv.BAR, 123);
strictEqual(otherEnv, env);
deepStrictEqual(argEnv, otherEnv);

// Using withEnv to replace the env...
const { env: otherEnv2 } = await withEnv({ BAZ: 1 }, async () => {
await scheduler.wait(0);
return import('child2');
});
strictEqual(otherEnv2.FOO, undefined);
strictEqual(otherEnv2.BAZ, 1);

// Original env is unmodified
strictEqual(env.BAZ, undefined);
},
};
11 changes: 10 additions & 1 deletion src/workerd/api/tests/importable-env-test.wd-test
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ const unitTests :Workerd.Config = (
worker = (
modules = [
(name = "worker", esModule = embed "importable-env-test.js"),
(name = "child", esModule = "export {env} from 'cloudflare:workers'"),
(name = "child", esModule = "import {env as live} from 'cloudflare:workers'; export const env = {...live};"),
(name = "child2", esModule = "import {env as live} from 'cloudflare:workers'; export const env = {...live};"),
],
compatibilityDate = "2025-02-01",
compatibilityFlags = [
Expand All @@ -15,6 +16,14 @@ const unitTests :Workerd.Config = (
],
bindings = [
(name = "FOO", text = "BAR"),
(name = "CACHE", memoryCache = (
id = "abc123",
limits = (
maxKeys = 10,
maxValueSize = 1024,
maxTotalValueSize = 1024,
),
))
],
)
),
Expand Down

0 comments on commit 66d9c5c

Please sign in to comment.