@tmrp/env 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (151) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +517 -0
  3. package/dist/create-env.d.ts +31 -0
  4. package/dist/create-env.d.ts.map +1 -0
  5. package/dist/create-env.js +35 -0
  6. package/dist/create-env.js.map +1 -0
  7. package/dist/effects/create-env-effect.d.ts +4 -0
  8. package/dist/effects/create-env-effect.d.ts.map +1 -0
  9. package/dist/effects/create-env-effect.js +8 -0
  10. package/dist/effects/create-env-effect.js.map +1 -0
  11. package/dist/effects/env-parse-value-effect.d.ts +4 -0
  12. package/dist/effects/env-parse-value-effect.d.ts.map +1 -0
  13. package/dist/effects/env-parse-value-effect.js +8 -0
  14. package/dist/effects/env-parse-value-effect.js.map +1 -0
  15. package/dist/effects/env-read-value-effect.d.ts +6 -0
  16. package/dist/effects/env-read-value-effect.d.ts.map +1 -0
  17. package/dist/effects/env-read-value-effect.js +3 -0
  18. package/dist/effects/env-read-value-effect.js.map +1 -0
  19. package/dist/effects/get-runtime-global-scope-effect.d.ts +26 -0
  20. package/dist/effects/get-runtime-global-scope-effect.d.ts.map +1 -0
  21. package/dist/effects/get-runtime-global-scope-effect.js +13 -0
  22. package/dist/effects/get-runtime-global-scope-effect.js.map +1 -0
  23. package/dist/effects/read-env-effect.d.ts +5 -0
  24. package/dist/effects/read-env-effect.d.ts.map +1 -0
  25. package/dist/effects/read-env-effect.js +42 -0
  26. package/dist/effects/read-env-effect.js.map +1 -0
  27. package/dist/index.d.ts +2 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +2 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/lib/read-env.d.ts +18 -0
  32. package/dist/lib/read-env.d.ts.map +1 -0
  33. package/dist/lib/read-env.js +65 -0
  34. package/dist/lib/read-env.js.map +1 -0
  35. package/dist/lib/schema.d.ts +26 -0
  36. package/dist/lib/schema.d.ts.map +1 -0
  37. package/dist/lib/schema.js +22 -0
  38. package/dist/lib/schema.js.map +1 -0
  39. package/dist/lib/types.d.ts +7 -0
  40. package/dist/lib/types.d.ts.map +1 -0
  41. package/dist/lib/types.js +2 -0
  42. package/dist/lib/types.js.map +1 -0
  43. package/dist/runtime/browser/create-browser-env.d.ts +26 -0
  44. package/dist/runtime/browser/create-browser-env.d.ts.map +1 -0
  45. package/dist/runtime/browser/create-browser-env.js +28 -0
  46. package/dist/runtime/browser/create-browser-env.js.map +1 -0
  47. package/dist/runtime/browser/lib/read-browser-env.d.ts +2 -0
  48. package/dist/runtime/browser/lib/read-browser-env.d.ts.map +1 -0
  49. package/dist/runtime/browser/lib/read-browser-env.js +15 -0
  50. package/dist/runtime/browser/lib/read-browser-env.js.map +1 -0
  51. package/dist/runtime/browser/lib/schema.d.ts +6 -0
  52. package/dist/runtime/browser/lib/schema.d.ts.map +1 -0
  53. package/dist/runtime/browser/lib/schema.js +7 -0
  54. package/dist/runtime/browser/lib/schema.js.map +1 -0
  55. package/dist/runtime/bun/create-bun-env.d.ts +24 -0
  56. package/dist/runtime/bun/create-bun-env.d.ts.map +1 -0
  57. package/dist/runtime/bun/create-bun-env.js +28 -0
  58. package/dist/runtime/bun/create-bun-env.js.map +1 -0
  59. package/dist/runtime/bun/lib/read-bun-env.d.ts +2 -0
  60. package/dist/runtime/bun/lib/read-bun-env.d.ts.map +1 -0
  61. package/dist/runtime/bun/lib/read-bun-env.js +14 -0
  62. package/dist/runtime/bun/lib/read-bun-env.js.map +1 -0
  63. package/dist/runtime/bun/lib/schema.d.ts +7 -0
  64. package/dist/runtime/bun/lib/schema.d.ts.map +1 -0
  65. package/dist/runtime/bun/lib/schema.js +9 -0
  66. package/dist/runtime/bun/lib/schema.js.map +1 -0
  67. package/dist/runtime/cloudflare/create-cloudflare-env.d.ts +32 -0
  68. package/dist/runtime/cloudflare/create-cloudflare-env.d.ts.map +1 -0
  69. package/dist/runtime/cloudflare/create-cloudflare-env.js +34 -0
  70. package/dist/runtime/cloudflare/create-cloudflare-env.js.map +1 -0
  71. package/dist/runtime/cloudflare/lib/read-cloudflare-env.d.ts +2 -0
  72. package/dist/runtime/cloudflare/lib/read-cloudflare-env.d.ts.map +1 -0
  73. package/dist/runtime/cloudflare/lib/read-cloudflare-env.js +14 -0
  74. package/dist/runtime/cloudflare/lib/read-cloudflare-env.js.map +1 -0
  75. package/dist/runtime/cloudflare/lib/schema.d.ts +5 -0
  76. package/dist/runtime/cloudflare/lib/schema.d.ts.map +1 -0
  77. package/dist/runtime/cloudflare/lib/schema.js +6 -0
  78. package/dist/runtime/cloudflare/lib/schema.js.map +1 -0
  79. package/dist/runtime/deno/create-deno-env.d.ts +23 -0
  80. package/dist/runtime/deno/create-deno-env.d.ts.map +1 -0
  81. package/dist/runtime/deno/create-deno-env.js +27 -0
  82. package/dist/runtime/deno/create-deno-env.js.map +1 -0
  83. package/dist/runtime/deno/lib/read-deno-env.d.ts +2 -0
  84. package/dist/runtime/deno/lib/read-deno-env.d.ts.map +1 -0
  85. package/dist/runtime/deno/lib/read-deno-env.js +13 -0
  86. package/dist/runtime/deno/lib/read-deno-env.js.map +1 -0
  87. package/dist/runtime/deno/lib/schema.d.ts +9 -0
  88. package/dist/runtime/deno/lib/schema.d.ts.map +1 -0
  89. package/dist/runtime/deno/lib/schema.js +14 -0
  90. package/dist/runtime/deno/lib/schema.js.map +1 -0
  91. package/dist/runtime/import-meta/create-import-meta-env.d.ts +28 -0
  92. package/dist/runtime/import-meta/create-import-meta-env.d.ts.map +1 -0
  93. package/dist/runtime/import-meta/create-import-meta-env.js +30 -0
  94. package/dist/runtime/import-meta/create-import-meta-env.js.map +1 -0
  95. package/dist/runtime/import-meta/lib/read-import-meta-env.d.ts +2 -0
  96. package/dist/runtime/import-meta/lib/read-import-meta-env.d.ts.map +1 -0
  97. package/dist/runtime/import-meta/lib/read-import-meta-env.js +14 -0
  98. package/dist/runtime/import-meta/lib/read-import-meta-env.js.map +1 -0
  99. package/dist/runtime/import-meta/lib/schema.d.ts +5 -0
  100. package/dist/runtime/import-meta/lib/schema.d.ts.map +1 -0
  101. package/dist/runtime/import-meta/lib/schema.js +6 -0
  102. package/dist/runtime/import-meta/lib/schema.js.map +1 -0
  103. package/dist/runtime/netlify/create-netlify-env.d.ts +25 -0
  104. package/dist/runtime/netlify/create-netlify-env.d.ts.map +1 -0
  105. package/dist/runtime/netlify/create-netlify-env.js +27 -0
  106. package/dist/runtime/netlify/create-netlify-env.js.map +1 -0
  107. package/dist/runtime/netlify/lib/read-netlify-env.d.ts +2 -0
  108. package/dist/runtime/netlify/lib/read-netlify-env.d.ts.map +1 -0
  109. package/dist/runtime/netlify/lib/read-netlify-env.js +14 -0
  110. package/dist/runtime/netlify/lib/read-netlify-env.js.map +1 -0
  111. package/dist/runtime/netlify/lib/schema.d.ts +9 -0
  112. package/dist/runtime/netlify/lib/schema.d.ts.map +1 -0
  113. package/dist/runtime/netlify/lib/schema.js +13 -0
  114. package/dist/runtime/netlify/lib/schema.js.map +1 -0
  115. package/dist/runtime/node/create-node-env.d.ts +27 -0
  116. package/dist/runtime/node/create-node-env.d.ts.map +1 -0
  117. package/dist/runtime/node/create-node-env.js +31 -0
  118. package/dist/runtime/node/create-node-env.js.map +1 -0
  119. package/dist/runtime/node/lib/read-node-env.d.ts +2 -0
  120. package/dist/runtime/node/lib/read-node-env.d.ts.map +1 -0
  121. package/dist/runtime/node/lib/read-node-env.js +13 -0
  122. package/dist/runtime/node/lib/read-node-env.js.map +1 -0
  123. package/dist/runtime/node/lib/schema.d.ts +7 -0
  124. package/dist/runtime/node/lib/schema.d.ts.map +1 -0
  125. package/dist/runtime/node/lib/schema.js +9 -0
  126. package/dist/runtime/node/lib/schema.js.map +1 -0
  127. package/dist/runtime/record/create-record-env.d.ts +29 -0
  128. package/dist/runtime/record/create-record-env.d.ts.map +1 -0
  129. package/dist/runtime/record/create-record-env.js +35 -0
  130. package/dist/runtime/record/create-record-env.js.map +1 -0
  131. package/dist/runtime/record/lib/read-record-env.d.ts +3 -0
  132. package/dist/runtime/record/lib/read-record-env.d.ts.map +1 -0
  133. package/dist/runtime/record/lib/read-record-env.js +8 -0
  134. package/dist/runtime/record/lib/read-record-env.js.map +1 -0
  135. package/dist/runtime/record/lib/schema.d.ts +3 -0
  136. package/dist/runtime/record/lib/schema.d.ts.map +1 -0
  137. package/dist/runtime/record/lib/schema.js +3 -0
  138. package/dist/runtime/record/lib/schema.js.map +1 -0
  139. package/dist/runtime/vercel/create-vercel-edge-env.d.ts +26 -0
  140. package/dist/runtime/vercel/create-vercel-edge-env.d.ts.map +1 -0
  141. package/dist/runtime/vercel/create-vercel-edge-env.js +28 -0
  142. package/dist/runtime/vercel/create-vercel-edge-env.js.map +1 -0
  143. package/dist/runtime/vercel/lib/read-vercel-edge-env.d.ts +2 -0
  144. package/dist/runtime/vercel/lib/read-vercel-edge-env.d.ts.map +1 -0
  145. package/dist/runtime/vercel/lib/read-vercel-edge-env.js +14 -0
  146. package/dist/runtime/vercel/lib/read-vercel-edge-env.js.map +1 -0
  147. package/dist/runtime/vercel/lib/schema.d.ts +5 -0
  148. package/dist/runtime/vercel/lib/schema.d.ts.map +1 -0
  149. package/dist/runtime/vercel/lib/schema.js +5 -0
  150. package/dist/runtime/vercel/lib/schema.js.map +1 -0
  151. package/package.json +112 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 TMRP
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,517 @@
1
+ # @tmrp/env
2
+
3
+ Type-safe environment variable parsing for TypeScript projects, powered by
4
+ [Zod](https://zod.dev/).
5
+
6
+ `@tmrp/env` reads environment variables from the current runtime or an
7
+ explicit env-like object, validates them with Zod schemas, and returns a
8
+ strongly typed object for application code. It supports Node.js, Deno, Bun,
9
+ Cloudflare Workers, Vercel Edge, Netlify, browser-injected config, and
10
+ `import.meta.env`-based toolchains.
11
+
12
+ ## Features
13
+
14
+ - Validate environment variables with any Zod schema.
15
+ - Infer TypeScript types from your schema definitions.
16
+ - Fail fast when required environment variables are missing.
17
+ - Trim string values before validation.
18
+ - Support Node.js `process.env`, Deno `Deno.env.get`, Bun `Bun.env`, and known
19
+ global env records.
20
+ - Validate explicit env records for edge runtimes, serverless bindings, browser
21
+ config, and `import.meta.env`.
22
+ - Use runtime-specific entry points when you know the target runtime.
23
+
24
+ ## Installation
25
+
26
+ ```sh
27
+ pnpm add @tmrp/env zod
28
+ ```
29
+
30
+ `zod` is used directly when defining schemas. This package also depends on
31
+ `effect` internally.
32
+
33
+ ## Quick Start
34
+
35
+ ```ts
36
+ import { createEnv } from "@tmrp/env";
37
+ import z from "zod";
38
+
39
+ export const env = createEnv({
40
+ API_URL: z.url(),
41
+ DATABASE_URL: z.url(),
42
+ NODE_ENV: z.enum(["development", "test", "production"]),
43
+ PORT: z.coerce.number().int().positive(),
44
+ });
45
+
46
+ // Fully typed:
47
+ env.API_URL; // string
48
+ env.PORT; // number
49
+ ```
50
+
51
+ If a variable is missing or does not match its schema, `createEnv` throws during
52
+ initialization. This makes configuration problems visible at startup instead of
53
+ later in application code.
54
+
55
+ ## Runtime Entry Points
56
+
57
+ | Entry point | Export | Runtime behavior |
58
+ | ----------------------- | --------------------- | --------------------------------------------------------------------------------- |
59
+ | `@tmrp/env` | `createEnv` | Detects supported runtime globals and reads from the available runtime. |
60
+ | `@tmrp/env/bun` | `createBunEnv` | Reads from Bun `Bun.env`. |
61
+ | `@tmrp/env/node` | `createNodeEnv` | Reads from Node.js `process.env`. |
62
+ | `@tmrp/env/deno` | `createDenoEnv` | Reads from Deno `Deno.env.get`. |
63
+ | `@tmrp/env/record` | `createRecordEnv` | Reads from an explicit object. |
64
+ | `@tmrp/env/cloudflare` | `createCloudflareEnv` | Reads from Cloudflare Worker bindings passed to a handler. |
65
+ | `@tmrp/env/vercel-edge` | `createVercelEdgeEnv` | Reads from an explicit env object in Vercel Edge code. |
66
+ | `@tmrp/env/netlify` | `createNetlifyEnv` | Reads from an explicit Netlify env object. |
67
+ | `@tmrp/env/browser` | `createBrowserEnv` | Reads from an explicit browser config object. |
68
+ | `@tmrp/env/import-meta` | `createImportMetaEnv` | Reads from `import.meta.env` for Vite, Astro, SvelteKit, Nuxt, and similar tools. |
69
+
70
+ ## Runtime Usage
71
+
72
+ ### Default Runtime Detection
73
+
74
+ Use `createEnv` when you want to read from whichever supported runtime globals
75
+ are available. It checks Bun, Vercel Edge, Netlify, Node, Deno, Cloudflare
76
+ global bindings, `import.meta.env` global shims, and browser config globals.
77
+
78
+ ```ts
79
+ import { createEnv } from "@tmrp/env";
80
+ import z from "zod";
81
+
82
+ const env = createEnv({
83
+ SERVICE_NAME: z.string().min(1),
84
+ });
85
+ ```
86
+
87
+ For runtimes that do not expose env values through standard globals, prefer the
88
+ explicit record entry points. The default reader also supports these optional
89
+ global records when your application intentionally exposes them:
90
+
91
+ | Global | Used by |
92
+ | -------------------------------- | -------------------------------------------------- |
93
+ | `globalThis.__CLOUDFLARE_ENV__` | Cloudflare-style bindings |
94
+ | `globalThis.__IMPORT_META_ENV__` | `import.meta.env` values copied to a global record |
95
+ | `globalThis.__APP_CONFIG__` | Browser application config |
96
+ | `globalThis.__ENV__` | Browser application config fallback |
97
+
98
+ ### Bun
99
+
100
+ Use `createBunEnv` when your application runs in Bun.
101
+
102
+ ```ts
103
+ import { createBunEnv } from "@tmrp/env/bun";
104
+ import z from "zod";
105
+
106
+ const env = createBunEnv({
107
+ DATABASE_URL: z.url(),
108
+ PORT: z.coerce.number().int().positive(),
109
+ });
110
+ ```
111
+
112
+ Values are read from `Bun.env`.
113
+
114
+ ### Node.js
115
+
116
+ Use `createNodeEnv` when your application runs in Node and you want the runtime
117
+ expectation to be explicit.
118
+
119
+ ```ts
120
+ import { createNodeEnv } from "@tmrp/env/node";
121
+ import z from "zod";
122
+
123
+ const env = createNodeEnv({
124
+ DATABASE_URL: z.url(),
125
+ LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]),
126
+ });
127
+ ```
128
+
129
+ Node values are read from `process.env`. If you use a local `.env` file, load it
130
+ before creating the environment object:
131
+
132
+ ```ts
133
+ import "dotenv/config";
134
+
135
+ import { createNodeEnv } from "@tmrp/env/node";
136
+ import z from "zod";
137
+
138
+ const env = createNodeEnv({
139
+ TEST_ENV: z.string(),
140
+ });
141
+ ```
142
+
143
+ ### Deno
144
+
145
+ Use `createDenoEnv` when your application runs in Deno.
146
+
147
+ ```ts
148
+ import { createDenoEnv } from "@tmrp/env/deno";
149
+ import z from "zod";
150
+
151
+ const env = createDenoEnv({
152
+ API_TOKEN: z.string().min(1),
153
+ });
154
+ ```
155
+
156
+ Deno environment access requires the appropriate permission, such as
157
+ `--allow-env`:
158
+
159
+ ```sh
160
+ deno run --allow-env src/main.ts
161
+ ```
162
+
163
+ ### Records And Passed Bindings
164
+
165
+ Use `createRecordEnv` for env-like objects that are passed to your code instead
166
+ of exposed globally.
167
+
168
+ ```ts
169
+ import { createRecordEnv } from "@tmrp/env/record";
170
+ import z from "zod";
171
+
172
+ const env = createRecordEnv(
173
+ {
174
+ API_URL: z.url(),
175
+ FEATURE_ENABLED: z.coerce.boolean(),
176
+ },
177
+ runtimeEnv
178
+ );
179
+ ```
180
+
181
+ String values are trimmed before validation. Non-string values are passed
182
+ directly to Zod, which is useful for framework-provided booleans or platform
183
+ bindings.
184
+
185
+ ### Cloudflare Workers
186
+
187
+ Cloudflare Workers pass bindings into handlers instead of exposing them on
188
+ `globalThis`.
189
+
190
+ ```ts
191
+ import { createCloudflareEnv } from "@tmrp/env/cloudflare";
192
+ import z from "zod";
193
+
194
+ export default {
195
+ fetch(request: Request, bindings: Record<string, unknown>) {
196
+ const env = createCloudflareEnv(
197
+ {
198
+ API_URL: z.url(),
199
+ API_TOKEN: z.string().min(1),
200
+ },
201
+ bindings
202
+ );
203
+
204
+ return fetch(env.API_URL, {
205
+ headers: { Authorization: `Bearer ${env.API_TOKEN}` },
206
+ });
207
+ },
208
+ };
209
+ ```
210
+
211
+ ### Vercel Edge
212
+
213
+ Use `createVercelEdgeEnv` with the env object available to your edge code.
214
+
215
+ ```ts
216
+ import { createVercelEdgeEnv } from "@tmrp/env/vercel-edge";
217
+ import z from "zod";
218
+
219
+ const env = createVercelEdgeEnv(
220
+ {
221
+ API_URL: z.url(),
222
+ },
223
+ process.env
224
+ );
225
+ ```
226
+
227
+ ### Netlify
228
+
229
+ Use `createNetlifyEnv` with the env object available to your Netlify function or
230
+ edge function.
231
+
232
+ ```ts
233
+ import { createNetlifyEnv } from "@tmrp/env/netlify";
234
+ import z from "zod";
235
+
236
+ const env = createNetlifyEnv(
237
+ {
238
+ API_URL: z.url(),
239
+ },
240
+ process.env
241
+ );
242
+ ```
243
+
244
+ ### Browser Config
245
+
246
+ Use `createBrowserEnv` for explicit browser configuration objects. This avoids
247
+ reading secrets from a client bundle by accident; only pass values that are safe
248
+ to expose publicly.
249
+
250
+ ```ts
251
+ import { createBrowserEnv } from "@tmrp/env/browser";
252
+ import z from "zod";
253
+
254
+ const env = createBrowserEnv(
255
+ {
256
+ PUBLIC_API_URL: z.url(),
257
+ },
258
+ globalThis.__APP_CONFIG__ as Record<string, unknown>
259
+ );
260
+ ```
261
+
262
+ ### `import.meta.env`
263
+
264
+ Use `createImportMetaEnv` for Vite, Astro, SvelteKit, Nuxt, and other tools that
265
+ expose environment values through `import.meta.env`.
266
+
267
+ ```ts
268
+ import { createImportMetaEnv } from "@tmrp/env/import-meta";
269
+ import z from "zod";
270
+
271
+ const env = createImportMetaEnv(
272
+ {
273
+ VITE_API_URL: z.url(),
274
+ DEV: z.boolean(),
275
+ },
276
+ import.meta.env
277
+ );
278
+ ```
279
+
280
+ ## Defining Schemas
281
+
282
+ Pass an object whose keys are environment variable names and whose values are
283
+ Zod schemas.
284
+
285
+ ```ts
286
+ const env = createEnv({
287
+ FEATURE_ENABLED: z.coerce.boolean(),
288
+ MAX_RETRIES: z.coerce.number().int().nonnegative(),
289
+ PUBLIC_URL: z.url(),
290
+ SECRET_KEY: z.string().min(32),
291
+ });
292
+ ```
293
+
294
+ The returned object is inferred from the schema object:
295
+
296
+ ```ts
297
+ env.FEATURE_ENABLED; // boolean
298
+ env.MAX_RETRIES; // number
299
+ env.PUBLIC_URL; // string
300
+ env.SECRET_KEY; // string
301
+ ```
302
+
303
+ Runtime global values are usually strings. Use Zod coercion or transforms when
304
+ you need booleans, numbers, dates, JSON parsing, or custom formats. Explicit
305
+ record-based entry points can also pass non-string values directly to Zod.
306
+
307
+ ## Required Values
308
+
309
+ All configured variables must exist in the runtime environment. Missing
310
+ variables fail before Zod validation runs, so Zod `.optional()` and `.default()`
311
+ schemas do not currently make an absent variable valid.
312
+
313
+ ```ts
314
+ const env = createEnv({
315
+ REQUIRED_TOKEN: z.string().min(1),
316
+ LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]),
317
+ });
318
+ ```
319
+
320
+ If you need defaults, define them in the runtime environment before calling
321
+ `createEnv`:
322
+
323
+ ```ts
324
+ process.env.LOG_LEVEL ??= "info";
325
+
326
+ const env = createEnv({
327
+ LOG_LEVEL: z.enum(["debug", "info", "warn", "error"]),
328
+ });
329
+ ```
330
+
331
+ ## Error Behavior
332
+
333
+ All creators run synchronously and throw on failure.
334
+
335
+ Missing variable error:
336
+
337
+ ```txt
338
+ Environment variable "DATABASE_URL" is not defined
339
+ ```
340
+
341
+ Validation error:
342
+
343
+ ```txt
344
+ Environment variable "PORT" failed validation: ...
345
+ ```
346
+
347
+ This behavior is intentional: configuration errors should fail application
348
+ startup immediately.
349
+
350
+ ## Example `.env`
351
+
352
+ ```dotenv
353
+ TEST_ENV="this-is-a-test-value"
354
+ DATABASE_URL="https://example.com/database"
355
+ PORT="3000"
356
+ NODE_ENV="development"
357
+ ```
358
+
359
+ An example file is included at `.env.example`.
360
+
361
+ ## API Reference
362
+
363
+ ### `createEnv(envKeys)`
364
+
365
+ Reads variables from the detected Bun, Node, or Deno runtime and validates them.
366
+
367
+ ```ts
368
+ function createEnv<const TEnvKeys extends Record<string, ZodType>>(
369
+ envKeys: TEnvKeys
370
+ ): { [K in keyof TEnvKeys]: z.infer<TEnvKeys[K]> };
371
+ ```
372
+
373
+ Use this from `@tmrp/env`.
374
+
375
+ ### `createBunEnv(envKeys)`
376
+
377
+ Reads variables from Bun `Bun.env` and validates them.
378
+
379
+ ```ts
380
+ function createBunEnv<const TEnvKeys extends Record<string, ZodType>>(
381
+ envKeys: TEnvKeys
382
+ ): { [K in keyof TEnvKeys]: z.infer<TEnvKeys[K]> };
383
+ ```
384
+
385
+ Use this from `@tmrp/env/bun`.
386
+
387
+ ### `createNodeEnv(envKeys)`
388
+
389
+ Reads variables from Node.js `process.env` and validates them.
390
+
391
+ ```ts
392
+ function createNodeEnv<const TEnvKeys extends Record<string, ZodType>>(
393
+ envKeys: TEnvKeys
394
+ ): { [K in keyof TEnvKeys]: z.infer<TEnvKeys[K]> };
395
+ ```
396
+
397
+ Use this from `@tmrp/env/node`.
398
+
399
+ ### `createDenoEnv(envKeys)`
400
+
401
+ Reads variables from Deno `Deno.env.get` and validates them.
402
+
403
+ ```ts
404
+ function createDenoEnv<const TEnvKeys extends Record<string, ZodType>>(
405
+ envKeys: TEnvKeys
406
+ ): { [K in keyof TEnvKeys]: z.infer<TEnvKeys[K]> };
407
+ ```
408
+
409
+ Use this from `@tmrp/env/deno`.
410
+
411
+ ### `createRecordEnv(envKeys, record)`
412
+
413
+ Reads variables from an explicit object and validates them.
414
+
415
+ ```ts
416
+ function createRecordEnv<const TEnvKeys extends Record<string, ZodType>>(
417
+ envKeys: TEnvKeys,
418
+ record: Record<string, unknown>
419
+ ): { [K in keyof TEnvKeys]: z.infer<TEnvKeys[K]> };
420
+ ```
421
+
422
+ Use this from `@tmrp/env/record`.
423
+
424
+ ### Platform Record Creators
425
+
426
+ These entry points all use the same explicit-record validation model, but expose
427
+ runtime-specific names for clearer application code:
428
+
429
+ | Entry point | Function |
430
+ | ----------------------- | --------------------------------------------- |
431
+ | `@tmrp/env/cloudflare` | `createCloudflareEnv(envKeys, bindings)` |
432
+ | `@tmrp/env/vercel-edge` | `createVercelEdgeEnv(envKeys, env)` |
433
+ | `@tmrp/env/netlify` | `createNetlifyEnv(envKeys, env)` |
434
+ | `@tmrp/env/browser` | `createBrowserEnv(envKeys, env)` |
435
+ | `@tmrp/env/import-meta` | `createImportMetaEnv(envKeys, importMetaEnv)` |
436
+
437
+ ## Development
438
+
439
+ This repository uses pnpm and TypeScript.
440
+
441
+ ### Requirements
442
+
443
+ - Node.js 24.x
444
+ - pnpm
445
+
446
+ The repository includes `.nvmrc` and `.npmrc` files for local Node version
447
+ alignment.
448
+
449
+ ### Install Dependencies
450
+
451
+ ```sh
452
+ pnpm install
453
+ ```
454
+
455
+ ### Typecheck
456
+
457
+ ```sh
458
+ pnpm typecheck
459
+ ```
460
+
461
+ ### Lint
462
+
463
+ ```sh
464
+ pnpm lint
465
+ ```
466
+
467
+ ### Test
468
+
469
+ ```sh
470
+ pnpm test
471
+ pnpm test:coverage
472
+ ```
473
+
474
+ Coverage thresholds are 100% for statements, branches, functions, and lines.
475
+
476
+ ### Build
477
+
478
+ ```sh
479
+ pnpm build
480
+ ```
481
+
482
+ ## Project Structure
483
+
484
+ ```txt
485
+ src/
486
+ create-env.ts Default runtime-aware environment creator
487
+ index.ts Default public export
488
+ effects/ Internal Effect-based read and parse pipeline
489
+ lib/ Shared schemas and types
490
+ runtime/
491
+ browser/ Browser config entry point
492
+ bun/ Bun-specific entry point and reader
493
+ cloudflare/ Cloudflare bindings entry point
494
+ deno/ Deno-specific entry point and reader
495
+ import-meta/ import.meta.env entry point
496
+ netlify/ Netlify env object entry point
497
+ node/ Node-specific entry point and reader
498
+ record/ Generic record/object entry point
499
+ vercel/ Vercel Edge env object entry point
500
+ ```
501
+
502
+ ## Notes And Limitations
503
+
504
+ - Values from strings are trimmed before validation.
505
+ - Reads are synchronous.
506
+ - The package validates configuration at creation time, not lazily.
507
+ - Runtime global values are usually strings; explicit records can also pass
508
+ non-string values to Zod.
509
+ - Missing environment variables currently fail before Zod defaults can be
510
+ applied.
511
+ - Browser and build-tool entry points validate values you explicitly pass to
512
+ them. The default reader can also use the documented global records when your
513
+ application intentionally exposes them.
514
+
515
+ ## License
516
+
517
+ MIT
@@ -0,0 +1,31 @@
1
+ import type { EnvKeys } from "./lib/types.js";
2
+ /**
3
+ * Creates a typed environment object from the currently available runtime.
4
+ *
5
+ * Use this entry point when your code can run across multiple supported
6
+ * runtimes, or when you do not want to bind the call site to a runtime-specific
7
+ * reader. The default reader detects supported globals such as Bun, Node, Deno,
8
+ * Vercel Edge, Netlify, Cloudflare-style global bindings, import-meta global
9
+ * shims, and browser config globals.
10
+ *
11
+ * For runtimes that pass env values directly to handlers, prefer the explicit
12
+ * record-based runtime helpers when possible.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * import { createEnv } from "@tmrp/env/default";
17
+ * import z from "zod";
18
+ *
19
+ * const env = createEnv({
20
+ * API_URL: z.url(),
21
+ * NODE_ENV: z.enum(["development", "test", "production"]),
22
+ * PORT: z.coerce.number().int().positive(),
23
+ * });
24
+ * ```
25
+ *
26
+ * @param envKeys Environment variable names mapped to Zod schemas.
27
+ * @returns A strongly typed object inferred from `envKeys`.
28
+ * @throws When a configured variable is missing or fails validation.
29
+ */
30
+ export declare function createEnv<const TEnvKeys extends EnvKeys>(envKeys: TEnvKeys): import("./lib/types.js").Env<TEnvKeys>;
31
+ //# sourceMappingURL=create-env.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-env.d.ts","sourceRoot":"","sources":["../src/create-env.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAM9C;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,wBAAgB,SAAS,CAAC,KAAK,CAAC,QAAQ,SAAS,OAAO,EAAE,OAAO,EAAE,QAAQ,0CAE1E"}
@@ -0,0 +1,35 @@
1
+ import { createEnvEffect } from "./effects/create-env-effect.js";
2
+ import { readEnvEffect } from "./effects/read-env-effect.js";
3
+ import { RuntimeGlobalsSchema } from "./lib/schema.js";
4
+ /**
5
+ * Creates a typed environment object from the currently available runtime.
6
+ *
7
+ * Use this entry point when your code can run across multiple supported
8
+ * runtimes, or when you do not want to bind the call site to a runtime-specific
9
+ * reader. The default reader detects supported globals such as Bun, Node, Deno,
10
+ * Vercel Edge, Netlify, Cloudflare-style global bindings, import-meta global
11
+ * shims, and browser config globals.
12
+ *
13
+ * For runtimes that pass env values directly to handlers, prefer the explicit
14
+ * record-based runtime helpers when possible.
15
+ *
16
+ * @example
17
+ * ```ts
18
+ * import { createEnv } from "@tmrp/env/default";
19
+ * import z from "zod";
20
+ *
21
+ * const env = createEnv({
22
+ * API_URL: z.url(),
23
+ * NODE_ENV: z.enum(["development", "test", "production"]),
24
+ * PORT: z.coerce.number().int().positive(),
25
+ * });
26
+ * ```
27
+ *
28
+ * @param envKeys Environment variable names mapped to Zod schemas.
29
+ * @returns A strongly typed object inferred from `envKeys`.
30
+ * @throws When a configured variable is missing or fails validation.
31
+ */
32
+ export function createEnv(envKeys) {
33
+ return createEnvEffect(envKeys, RuntimeGlobalsSchema, readEnvEffect);
34
+ }
35
+ //# sourceMappingURL=create-env.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-env.js","sourceRoot":"","sources":["../src/create-env.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,8BAA8B,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,MAAM,UAAU,SAAS,CAAiC,OAAiB;IACzE,OAAO,eAAe,CAAC,OAAO,EAAE,oBAAoB,EAAE,aAAa,CAAC,CAAC;AACvE,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { RuntimeGlobalsSchemaType } from "../lib/schema.js";
2
+ import type { Env, EnvKeys } from "../lib/types.js";
3
+ export declare function createEnvEffect<const TEnvKeys extends EnvKeys>(envKeys: TEnvKeys, runtimeSchema: RuntimeGlobalsSchemaType, runtimeEnvReadEffect: (key: string, schema: RuntimeGlobalsSchemaType) => unknown): Env<TEnvKeys>;
4
+ //# sourceMappingURL=create-env-effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-env-effect.d.ts","sourceRoot":"","sources":["../../src/effects/create-env-effect.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,KAAK,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAC;AAKpD,wBAAgB,eAAe,CAAC,KAAK,CAAC,QAAQ,SAAS,OAAO,EAC5D,OAAO,EAAE,QAAQ,EACjB,aAAa,EAAE,wBAAwB,EACvC,oBAAoB,EAAE,CACpB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,wBAAwB,KAC7B,OAAO,GAaE,GAAG,CAAC,QAAQ,CAAC,CAC5B"}
@@ -0,0 +1,8 @@
1
+ import { Effect } from "effect";
2
+ import { envParseValueEffect } from "./env-parse-value-effect.js";
3
+ import { envReadValueEffect } from "./env-read-value-effect.js";
4
+ export function createEnvEffect(envKeys, runtimeSchema, runtimeEnvReadEffect) {
5
+ const env = Effect.runSync(Effect.forEach(Object.entries(envKeys), ([key, schema]) => envReadValueEffect(key, (env) => runtimeEnvReadEffect(env, runtimeSchema)).pipe(Effect.flatMap((value) => envParseValueEffect(key, schema, value)), Effect.map((value) => [key, value]))).pipe(Effect.map((entries) => Object.fromEntries(entries))));
6
+ return env;
7
+ }
8
+ //# sourceMappingURL=create-env-effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-env-effect.js","sourceRoot":"","sources":["../../src/effects/create-env-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAKhC,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAEhE,MAAM,UAAU,eAAe,CAC7B,OAAiB,EACjB,aAAuC,EACvC,oBAGY;IAEZ,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CACxB,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,MAAM,CAAC,EAAE,EAAE,CACxD,kBAAkB,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,EAAE,CAC9B,oBAAoB,CAAC,GAAG,EAAE,aAAa,CAAC,CACzC,CAAC,IAAI,CACJ,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,CAAC,EAClE,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,GAAG,EAAE,KAAK,CAAU,CAAC,CAC7C,CACF,CAAC,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAC7D,CAAC;IAEF,OAAO,GAAoB,CAAC;AAC9B,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { ZodType } from "zod";
2
+ import { Effect } from "effect";
3
+ export declare const envParseValueEffect: (key: string, schema: ZodType, value: unknown) => Effect.Effect<unknown, Error, never>;
4
+ //# sourceMappingURL=env-parse-value-effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-parse-value-effect.d.ts","sourceRoot":"","sources":["../../src/effects/env-parse-value-effect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,KAAK,CAAC;AAEnC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,eAAO,MAAM,mBAAmB,GAC9B,KAAK,MAAM,EACX,QAAQ,OAAO,EACf,OAAO,OAAO,yCAQZ,CAAC"}
@@ -0,0 +1,8 @@
1
+ import { Effect } from "effect";
2
+ export const envParseValueEffect = (key, schema, value) => Effect.try({
3
+ try: () => schema.parse(value),
4
+ catch: (error) => new Error(`Environment variable "${key}" failed validation: ${error}`, {
5
+ cause: error,
6
+ }),
7
+ });
8
+ //# sourceMappingURL=env-parse-value-effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-parse-value-effect.js","sourceRoot":"","sources":["../../src/effects/env-parse-value-effect.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,GAAW,EACX,MAAe,EACf,KAAc,EACd,EAAE,CACF,MAAM,CAAC,GAAG,CAAC;IACT,GAAG,EAAE,GAAG,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC;IAC9B,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CACf,IAAI,KAAK,CAAC,yBAAyB,GAAG,wBAAwB,KAAK,EAAE,EAAE;QACrE,KAAK,EAAE,KAAK;KACb,CAAC;CACL,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import { Effect } from "effect";
2
+ type Key = string;
3
+ type readRuntimeEnvEffect = (key: Key) => unknown;
4
+ export declare const envReadValueEffect: (key: Key, readRuntimeEnvEffect: readRuntimeEnvEffect) => Effect.Effect<{}, Error, never>;
5
+ export {};
6
+ //# sourceMappingURL=env-read-value-effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-read-value-effect.d.ts","sourceRoot":"","sources":["../../src/effects/env-read-value-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAEhC,KAAK,GAAG,GAAG,MAAM,CAAC;AAElB,KAAK,oBAAoB,GAAG,CAAC,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC;AAElD,eAAO,MAAM,kBAAkB,GAC7B,KAAK,GAAG,EACR,sBAAsB,oBAAoB,oCAMzC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Effect } from "effect";
2
+ export const envReadValueEffect = (key, readRuntimeEnvEffect) => Effect.fromNullable(readRuntimeEnvEffect(key)).pipe(Effect.mapError(() => new Error(`Environment variable "${key}" is not defined`)));
3
+ //# sourceMappingURL=env-read-value-effect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-read-value-effect.js","sourceRoot":"","sources":["../../src/effects/env-read-value-effect.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAMhC,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,GAAQ,EACR,oBAA0C,EAC1C,EAAE,CACF,MAAM,CAAC,YAAY,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CACjD,MAAM,CAAC,QAAQ,CACb,GAAG,EAAE,CAAC,IAAI,KAAK,CAAC,yBAAyB,GAAG,kBAAkB,CAAC,CAChE,CACF,CAAC"}