@@ -20,13 +20,105 @@ export function nextTick(cb: Function, ...args: unknown[]) {
2020 } ) ;
2121}
2222
23- // Note that there is no process-level environment in workers so the process.env
24- // object is initially empty. This is different from Node.js where process.env
25- // picks up values from the operating system environment. The configured bindings
26- // for the worker are accessible from the env argument passed into the fetch
27- // handler and have no impact here.
23+ // Decide if a value can round-trip to JSON without losing any information.
24+ function isJsonSerializable (
25+ value : any ,
26+ seen : Set < Object > = new Set ( )
27+ ) : boolean {
28+ switch ( typeof value ) {
29+ case 'boolean' :
30+ case 'number' :
31+ case 'string' :
32+ return true ;
2833
29- export const env = new Proxy ( utilImpl . getEnvObject ( ) , {
34+ case 'object' : {
35+ if ( value === null ) {
36+ return true ;
37+ }
38+
39+ if ( seen . has ( value ) ) {
40+ // Don't allow cycles or aliases. (Non-cyclic aliases technically could be OK, but a
41+ // round trip to JSON would lose the fact that they are aliases.)
42+ return false ;
43+ }
44+ seen . add ( value ) ;
45+
46+ if ( typeof value . toJSON === 'function' ) {
47+ // This type is explicitly designed to be JSON-serialized so we'll accept it.
48+ return true ;
49+ }
50+
51+ // We only consider objects to be serializable if they are plain objects or plain arrays.
52+ // Technically, JSON can serialize any subclass of Object (as well as objects with null
53+ // prototypes), but the round trip would lose information about the original type. Hence,
54+ // we assume that any env var containing such things is not intended to be appear as JSON
55+ // in process.env. For example, we wouldn't want a KV namespace to show up in process.env as
56+ // "{}" -- this would be weird.
57+ switch ( Object . getPrototypeOf ( value ) ) {
58+ case Object . prototype :
59+ // Note that Object.values() only returns string-keyed values, not symbol-keyed.
60+ return Object . values ( value ) . every ( ( prop ) =>
61+ isJsonSerializable ( prop , seen )
62+ ) ;
63+ case Array . prototype :
64+ return ( < Array < unknown > > value ) . every ( ( elem ) =>
65+ isJsonSerializable ( elem , seen )
66+ ) ;
67+ default :
68+ return false ;
69+ }
70+ }
71+
72+ default :
73+ return false ;
74+ }
75+ }
76+
77+ function getInitialEnv ( ) {
78+ let env : Record < string , string > = { } ;
79+ for ( let [ key , value ] of Object . entries ( utilImpl . getEnvObject ( ) ) ) {
80+ // Workers environment variables can have a variety of types, but process.env vars are
81+ // strictly strings. We want to convert our workers env into process.env, but allowing
82+ // process.env to contain non-strings would probably break Node apps.
83+ //
84+ // As a compromise, we say:
85+ // - Workers env vars that are plain strings are unchanged in process.env.
86+ // - Workers env vars that can be represented as JSON will be JSON-stringified in process.env.
87+ // - Anything else will be omitted.
88+ //
89+ // Note that you might argue that, at the config layer, it's possible to differentiate between
90+ // plain strings and JSON values that evaluated to strings. Wouldn't it be nice if we could
91+ // check which way the binding was originally configured in order to decide whether to
92+ // represent it plain or as JSON here. However, there is no way to tell just by looking at
93+ // the `env` object inside a Worker whether a particular var was originally configured as
94+ // plain text, or as JSON that evaluated to a string. Either way, you just get a string. And
95+ // indeed, the Workers Runtime itself does not necessarily know this. In many cases it does
96+ // know, but in general the abstraction the Runtime intends to provide is that `env` is just
97+ // a JavaScript object, and how exactly the contents were originally represented is not
98+ // intended to be conveyed. This is important because, for example, we could extend dynamic
99+ // dispatch bindings in the future such that the caller can specify `env` directly, and in
100+ // that case the caller would simply specify a JS object, without JSON or any other
101+ // serialization involved. In this case, there would be no way to know if a string var was
102+ // "supposed to be" raw text vs. JSON.
103+ //
104+ // So, we have to do the best we can given just what we know -- the JavaScript object that is
105+ // `env`.
106+ //
107+ // As a consolation, this is consistent with how variables are defined in wrangler.toml: you
108+ // do not explicitly specify whether a variable is text or JSON. If you define a variable with
109+ // a simple string value, it gets configured as a text var. If you specify an object, then it's
110+ // configured as JSON.
111+
112+ if ( typeof value === 'string' ) {
113+ env [ key ] = value ;
114+ } else if ( isJsonSerializable ( value ) ) {
115+ env [ key ] = JSON . stringify ( value ) ;
116+ }
117+ }
118+ return env ;
119+ }
120+
121+ export const env = new Proxy ( getInitialEnv ( ) , {
30122 // Per Node.js rules. process.env values must be coerced to strings.
31123 // When defined using defineProperty, the property descriptor must be writable,
32124 // configurable, and enumerable using just a falsy check. Getters and setters
0 commit comments