@arki/env 0.0.9 → 0.2.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.
- package/CHANGELOG.md +196 -0
- package/README.md +231 -62
- package/dist/api.json +597 -0
- package/dist/core/define-env.d.ts +29 -4
- package/dist/core/define-env.d.ts.map +1 -1
- package/dist/core/define-env.js +85 -15
- package/dist/core/define-env.js.map +1 -1
- package/dist/core/describe.d.ts +29 -0
- package/dist/core/describe.d.ts.map +1 -0
- package/dist/core/describe.js +25 -0
- package/dist/core/describe.js.map +1 -0
- package/dist/core/errors.d.ts +10 -0
- package/dist/core/errors.d.ts.map +1 -0
- package/dist/core/errors.js +13 -0
- package/dist/core/errors.js.map +1 -0
- package/dist/core/index.d.ts +7 -2
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +6 -2
- package/dist/core/index.js.map +1 -1
- package/dist/core/secret.d.ts +56 -0
- package/dist/core/secret.d.ts.map +1 -0
- package/dist/core/secret.js +85 -0
- package/dist/core/secret.js.map +1 -0
- package/dist/errors.json +128 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/node/codec.d.ts +63 -0
- package/dist/node/codec.d.ts.map +1 -0
- package/dist/node/codec.js +182 -0
- package/dist/node/codec.js.map +1 -0
- package/dist/node/edit.d.ts +112 -0
- package/dist/node/edit.d.ts.map +1 -0
- package/dist/node/edit.js +268 -0
- package/dist/node/edit.js.map +1 -0
- package/dist/node/errors.d.ts +119 -0
- package/dist/node/errors.d.ts.map +1 -0
- package/dist/node/errors.js +144 -0
- package/dist/node/errors.js.map +1 -0
- package/dist/node/files.d.ts +9 -0
- package/dist/node/files.d.ts.map +1 -0
- package/dist/node/files.js +35 -0
- package/dist/node/files.js.map +1 -0
- package/dist/node/index.d.ts +20 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +13 -0
- package/dist/node/index.js.map +1 -0
- package/dist/node/key-schemas.d.ts +25 -0
- package/dist/node/key-schemas.d.ts.map +1 -0
- package/dist/node/key-schemas.js +38 -0
- package/dist/node/key-schemas.js.map +1 -0
- package/dist/node/keys.d.ts +54 -0
- package/dist/node/keys.d.ts.map +1 -0
- package/dist/node/keys.js +76 -0
- package/dist/node/keys.js.map +1 -0
- package/dist/node/load.d.ts +103 -0
- package/dist/node/load.d.ts.map +1 -0
- package/dist/node/load.js +306 -0
- package/dist/node/load.js.map +1 -0
- package/dist/node/operation-telemetry.d.ts +3 -0
- package/dist/node/operation-telemetry.d.ts.map +1 -0
- package/dist/node/operation-telemetry.js +53 -0
- package/dist/node/operation-telemetry.js.map +1 -0
- package/dist/node/references.d.ts +16 -0
- package/dist/node/references.d.ts.map +1 -0
- package/dist/node/references.js +72 -0
- package/dist/node/references.js.map +1 -0
- package/dist/node/register.d.ts +2 -0
- package/dist/node/register.d.ts.map +1 -0
- package/dist/node/register.js +19 -0
- package/dist/node/register.js.map +1 -0
- package/dist/{contracts → presets}/common.d.ts +1 -1
- package/dist/presets/common.d.ts.map +1 -0
- package/dist/{contracts → presets}/common.js +4 -8
- package/dist/presets/common.js.map +1 -0
- package/dist/{contracts → presets}/index.d.ts +5 -5
- package/dist/presets/index.d.ts.map +1 -0
- package/dist/presets/index.js +15 -0
- package/dist/presets/index.js.map +1 -0
- package/dist/{contracts → presets}/parsing.d.ts +2 -2
- package/dist/presets/parsing.d.ts.map +1 -0
- package/dist/{contracts → presets}/parsing.js +13 -5
- package/dist/presets/parsing.js.map +1 -0
- package/dist/{contracts → presets}/urls.d.ts +1 -1
- package/dist/presets/urls.d.ts.map +1 -0
- package/dist/{contracts → presets}/urls.js +1 -1
- package/dist/presets/urls.js.map +1 -0
- package/docs/agent-guide.md +121 -0
- package/docs/upgrades/0.0.11-to-0.1.0.md +62 -0
- package/docs/upgrades/0.1.0-to-0.2.0.md +73 -0
- package/docs/upgrades/typed-node-outcomes.md +13 -0
- package/llms.txt +30 -0
- package/package.json +27 -20
- package/src/core/define-env.ts +101 -20
- package/src/core/describe.ts +52 -0
- package/src/core/errors.ts +14 -0
- package/src/core/index.ts +7 -2
- package/src/core/secret.ts +127 -0
- package/src/index.ts +3 -2
- package/src/node/codec.ts +220 -0
- package/src/node/edit.ts +279 -0
- package/src/node/errors.ts +147 -0
- package/src/node/files.ts +41 -0
- package/src/node/index.ts +48 -0
- package/src/node/key-schemas.ts +44 -0
- package/src/node/keys.ts +93 -0
- package/src/node/load.ts +386 -0
- package/src/node/operation-telemetry.ts +56 -0
- package/src/node/references.ts +90 -0
- package/src/node/register.ts +18 -0
- package/src/{contracts → presets}/common.ts +4 -8
- package/src/presets/index.ts +18 -0
- package/src/{contracts → presets}/parsing.ts +12 -5
- package/src/{contracts → presets}/urls.ts +1 -1
- package/dist/contracts/common.d.ts.map +0 -1
- package/dist/contracts/common.js.map +0 -1
- package/dist/contracts/index.d.ts.map +0 -1
- package/dist/contracts/index.js +0 -14
- package/dist/contracts/index.js.map +0 -1
- package/dist/contracts/parsing.d.ts.map +0 -1
- package/dist/contracts/parsing.js.map +0 -1
- package/dist/contracts/urls.d.ts.map +0 -1
- package/dist/contracts/urls.js.map +0 -1
- package/dist/dot.d.ts +0 -67
- package/dist/dot.d.ts.map +0 -1
- package/dist/dot.js +0 -65
- package/dist/dot.js.map +0 -1
- package/src/contracts/index.ts +0 -17
- package/src/dot.ts +0 -94
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# @arki/env
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 307dcf6: Encrypted env files in the dotenvx 2.x format. New `@arki/env/node`: `loadEnvFiles()` reads `.env.<environment>` and `.env.local` from the repository root down to the working directory, decrypts `encrypted:` values (via `eciesjs`, byte-compatible with dotenvx), resolves `op://`, `infisical://` and `doppler://` references, and writes what the real environment does not set into it (real values win; differing ones are reported as shadowed). Also the codec and file edits behind `dot env`. New side-effect entry `@arki/env/node/register`. `@arki/env/core` adds `describeEnv`, `isSecretSchema` and `SECRET_SCHEMA`, and `defineEnv` now refuses a value that is still `encrypted:` instead of accepting ciphertext as a string. New codes `ARKI_ENV_FILES_E001`–`E003`.
|
|
8
|
+
- a7a4a9d: Fix the foundation findings of the T08 review.
|
|
9
|
+
|
|
10
|
+
- `@arki/log`: `maskSecrets` and `otelSink` handle an object that contains itself (the cycle becomes `[Circular]`) instead of dropping the record. A `.to('name')` logger whose channel is not defined no longer throws into the caller at log time; the fault is reported once and the channel is skipped. `file()` throws a coded `LogError` (`ARKI_LOG_E006`) when it cannot open its path.
|
|
11
|
+
- `@arki/async`: `debounce` and `throttle` take an `onError` option. An error thrown by the callback, or a promise it returns that rejects, goes there. Without it the error is rethrown from a microtask. The schedule stays consistent and a leading call never throws into the caller.
|
|
12
|
+
- `@arki/env`: `rotateEnvFiles` returns an `InvalidInput` failure for an empty list or files of two environments (it threw a plain `Error`). The default `onValidationError` of `defineEnv` throws an `UnexpectedFailure` that carries the new `env.invalid_environment` failure (`InvalidEnvironment`).
|
|
13
|
+
|
|
14
|
+
- 818fd9c: The Dot adapter moved from `@arki/env/dot` to `@arki/dot/env`. `@arki/env` drops its `@arki/dot` peer; `@arki/dot` depends on `@arki/env`. `@arki/env/core` exports `formatIssues`. Migration: `packages/env/docs/upgrades/0.1.0-to-0.2.0.md`.
|
|
15
|
+
- 0511f4a: Return precise canonical env node outcomes, sanitize provider diagnostics, validate distinct public/private key domains and preserve recovery keys across partial rotation. Share bounded load deadlines and cooperative cancellation with provider reads, and instrument owned node/provider operations through OpenTelemetry without installing an SDK. Migrate Dot env commands and database preparation to handle the returned outcomes.
|
|
16
|
+
|
|
17
|
+
Keep explicit zero/empty defaults and report JSON parsing/output rejection through schema issues. Storage and crypto remain dotenvx-compatible; see `@arki/env/docs/upgrades/typed-node-outcomes.md`.
|
|
18
|
+
|
|
19
|
+
no demo impact: node env operations are exercised through the CLI and package tests.
|
|
20
|
+
|
|
21
|
+
- cd969bd: Breaking: `@arki/env/contracts` is now `@arki/env/presets`. `contracts` is `presets` (also from `@arki/env` and `@arki/env/core`), and `commonContracts`, `urlContracts`, `parseContracts` are `commonPresets`, `urlPresets`, `parsePresets`. The validators are unchanged. See `docs/upgrades/0.1.0-to-0.2.0.md`.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- 1e47612: `Lazy<T>` and `resolveLazy` move from `@arki/ts` to `@arki/dot/plugin` as `Deferred<T>` and `resolveDeferred`. They are the "value now, or a thunk the adapter calls at boot" shape that Dot adapters accept, and the old name clashed with Dot's own `Lazy<Value, Cleanup>` service handle. Behaviour is unchanged. `@arki/ts` no longer exports them; to upgrade, import `Deferred` and `resolveDeferred` from `@arki/dot/plugin`.
|
|
26
|
+
|
|
27
|
+
`@arki/http` (`port`) and `@arki/kv` (`kv()`, `kv.optional()`) now type their deferred options as `Deferred<T>`, the same shape as before, and no longer depend on `@arki/ts`. `@arki/env` drops an `@arki/ts` dependency it never imported.
|
|
28
|
+
|
|
29
|
+
demo impact: none (neither demo imports `Lazy` or `resolveLazy`).
|
|
30
|
+
|
|
31
|
+
## 0.1.0
|
|
32
|
+
|
|
33
|
+
### Minor Changes
|
|
34
|
+
|
|
35
|
+
- 1e15576: `defineEnv()` `server`, `client` and `shared` take records of any Standard Schema (Zod, Valibot, ArkType, hand-written), not only `ZodType`; values are typed as each schema's output.
|
|
36
|
+
`@arki/env/dot`: `EnvDotOptions`, `EnvServices` and `env()` take `Record<string, StandardSchemaV1>` instead of `Record<string, ZodType>`.
|
|
37
|
+
- 75d6fb9: **Validation now runs in production by default.** `defineEnv` used to skip validation whenever `NODE_ENV !== 'development'`, so an app without an explicit `skipValidation` booted production on an unchecked config. The default now skips only when `CI` is set (Docker build stages) or during `lint` runs, the same rule most apps already passed by hand. Apps that set `options.skipValidation` are unaffected.
|
|
38
|
+
|
|
39
|
+
New `secret(schema)`, `reveal(value)` and `Redacted` (from `@arki/env/core`): a secret variable becomes a `Redacted` value that prints as `[redacted]` in logs, JSON, template strings and `util.inspect`, and is read with a deliberate `reveal(env.KEY)`. `reveal` also accepts the raw string, for runs where validation is skipped.
|
|
40
|
+
|
|
41
|
+
- cc7facf: Breaking: `defineEnv` now validates in every environment. `CI` and `lint` runs no longer skip validation. The only implicit opt-out is `SKIP_ENV_VALIDATION=1` (exactly `1`); set it only in build stages that run without secrets. `options.skipValidation` still overrides it. The `@arki/env/dot` plugin now validates at boot and fails with `ARKI_ENV_PLUGIN_E001` (new `ENV_PLUGIN_ERROR_CODES`), and `EnvServices['env']` is derived from `defineEnv` without a cast.
|
|
42
|
+
- a67cd66: `secret()` takes any Standard Schema (zod, valibot, arktype, hand-written) and returns a Standard Schema, not a zod schema. `.optional()` is still there: it accepts a missing variable and leaves `undefined` unwrapped. The result no longer has zod methods such as `.parse`; use `secret(schema)['~standard'].validate(value)`.
|
|
43
|
+
|
|
44
|
+
A rejected secret's issues keep their path but get a fixed message, because some validators (valibot) repeat the received value in theirs and `defineEnv` logs issues.
|
|
45
|
+
|
|
46
|
+
### Patch Changes
|
|
47
|
+
|
|
48
|
+
- 064e096: Make failure the acyclic validation foundation. Schema parsing now returns exact typed validation failures, and failure revival returns typed protocol/validator defects instead of throwing. Add validated versioned JSON envelopes; retain legacy envelope reads while rejecting non-JSON data and changing output transforms. oRPC error maps validate output-only wire data.
|
|
49
|
+
|
|
50
|
+
Migrate consumers to check failures, propagate them through Journal event/write/replay and queue/mail operations, and use explicit exception bridges at foreign callbacks. Journal rejects invalid event entries before inserting them. See `@arki/failure/docs/upgrade-typed-validation.md` for breaking API and stored-data migration details.
|
|
51
|
+
|
|
52
|
+
- 652eb4b: `env()`, `resilience()`, `auth()`, `journal()`, `journalProjections()`, `queue()`, `durableScheduler()` and `outboxPlugin()` keep their plugin name in the type, so their handles carry `provider: 'env' | 'resilience' | …` instead of `string` and the feature builder's handle-identity checks apply around them. The three queue plugins gain an overload: with no `name` option the type carries the default name (`'queue'`, `'durable-scheduler'`, `'outbox'`); with `name: 'jobs'` it carries `'jobs'`.
|
|
53
|
+
- cbefc52: Dot plugins no longer pass a hard-coded `version` (the field was removed from `@arki/dot`).
|
|
54
|
+
- 5bc6390: `env()` publishes through a `provides` factory, so `envPlugin.provides.env` is a typed handle before boot (`@arki/dot` plugin handles).
|
|
55
|
+
- fd18765: `defineEnv` reads `globalThis.process?.env` instead of the bare `process` global, so it no longer throws a ReferenceError on engines without `process` (React Native Hermes, browsers). It still reads nothing at import. Pass `runtimeEnv` on those engines. Added the library lint preset and a cross-runtime smoke test.
|
|
56
|
+
- fe5a391: README follows the docs standard (install, quick start, limits, failure behaviour), with `llms.txt`, an agent guide, `AGENTS.md`, `test:docs` and the 0.0.11 to 0.1.0 upgrade guide. `ARKI_ENV_PLUGIN_E001` gains `@fix` and `@verify` tags.
|
|
57
|
+
|
|
58
|
+
no demo impact: documentation and doc-example changes only, runtime behaviour unchanged
|
|
59
|
+
|
|
60
|
+
- Updated dependencies [dcf2cef]
|
|
61
|
+
- Updated dependencies [afecc7a]
|
|
62
|
+
- Updated dependencies [5bc6390]
|
|
63
|
+
- Updated dependencies [899d0a9]
|
|
64
|
+
- Updated dependencies [ce8d707]
|
|
65
|
+
- Updated dependencies [92b38ed]
|
|
66
|
+
- Updated dependencies [9a4babb]
|
|
67
|
+
- Updated dependencies [80a4aa5]
|
|
68
|
+
- Updated dependencies [232d3e8]
|
|
69
|
+
- Updated dependencies [69d054f]
|
|
70
|
+
- Updated dependencies [cbefc52]
|
|
71
|
+
- Updated dependencies [7b19289]
|
|
72
|
+
- Updated dependencies [923124f]
|
|
73
|
+
- Updated dependencies [5721742]
|
|
74
|
+
- Updated dependencies [01400a0]
|
|
75
|
+
- Updated dependencies [0ab11d8]
|
|
76
|
+
- Updated dependencies [a531063]
|
|
77
|
+
- Updated dependencies [9b35520]
|
|
78
|
+
- Updated dependencies [5721742]
|
|
79
|
+
- Updated dependencies [232d3e8]
|
|
80
|
+
- Updated dependencies [9192d0e]
|
|
81
|
+
- Updated dependencies [3230f02]
|
|
82
|
+
- Updated dependencies [232d3e8]
|
|
83
|
+
- Updated dependencies [232d3e8]
|
|
84
|
+
- Updated dependencies [932c9e4]
|
|
85
|
+
- Updated dependencies [232d3e8]
|
|
86
|
+
- Updated dependencies [cc656e3]
|
|
87
|
+
- Updated dependencies [ff8e384]
|
|
88
|
+
- Updated dependencies [ce8d707]
|
|
89
|
+
- Updated dependencies [847f025]
|
|
90
|
+
- Updated dependencies [232d3e8]
|
|
91
|
+
- Updated dependencies [d9864f3]
|
|
92
|
+
- Updated dependencies [742f4c3]
|
|
93
|
+
- Updated dependencies [115aa87]
|
|
94
|
+
- Updated dependencies [6e4d7da]
|
|
95
|
+
- Updated dependencies [8b3cc80]
|
|
96
|
+
- @arki/dot@0.7.0
|
|
97
|
+
|
|
98
|
+
## 0.0.11
|
|
99
|
+
|
|
100
|
+
### Patch Changes
|
|
101
|
+
|
|
102
|
+
- @arki/dot@0.6.1
|
|
103
|
+
|
|
104
|
+
## 0.0.10
|
|
105
|
+
|
|
106
|
+
### Patch Changes
|
|
107
|
+
|
|
108
|
+
- Updated dependencies [228b4f5]
|
|
109
|
+
- Updated dependencies [9fa13ce]
|
|
110
|
+
- @arki/dot@0.6.0
|
|
111
|
+
|
|
112
|
+
## 0.0.9
|
|
113
|
+
|
|
114
|
+
### Patch Changes
|
|
115
|
+
|
|
116
|
+
- 91efd52: Reading a value from a `defineEnv()` env is faster once the env is in regular use. After 64 reads, an env whose values are private to it (validated on the server, or skipped with the runtime env `defineEnv` built itself) copies its values into the lazy object and reads them directly, instead of going through two proxies on every read. Writes and deletes through the env update the copy too. An env over your own `runtimeEnv` with validation skipped still reads it live.
|
|
117
|
+
- Updated dependencies [0685dfa]
|
|
118
|
+
- Updated dependencies
|
|
119
|
+
- Updated dependencies [2854df5]
|
|
120
|
+
- @arki/dot@0.5.0
|
|
121
|
+
|
|
122
|
+
## 0.0.8
|
|
123
|
+
|
|
124
|
+
### Patch Changes
|
|
125
|
+
|
|
126
|
+
- Dependency refresh: `@t3-oss/env-core` ^0.13.11, `zod` 4.6.5. No API changes.
|
|
127
|
+
|
|
128
|
+
## 0.0.7
|
|
129
|
+
|
|
130
|
+
### Patch Changes
|
|
131
|
+
|
|
132
|
+
- Lazy primitives family + client-surface hardening.
|
|
133
|
+
|
|
134
|
+
- `@arki/ts`: new `once()` (zero-arg memo; memoizes `undefined`, sync throws retry, promises shared), hardened `lazyObject()` (`set`/`deleteProperty`/`getPrototypeOf` traps — writes land on the real instance, `instanceof` answers truthfully), new `Lazy<T>`/`resolveLazy` deferred-config vocabulary, and a `./testing` entry with `expectImportPure()` (subprocess import-purity harness).
|
|
135
|
+
- `@arki/env`: `defineEnv` now delegates its lazy proxy to `lazyObject` (single trap implementation).
|
|
136
|
+
- `@arki/kv`: `kv()` accepts a whole-options thunk — `kv(() => ({ url: env.REDIS_URL }))` — keeping declarations import-pure; per-field `url` thunk kept for compatibility.
|
|
137
|
+
- `@arki/http`: `port` adopts `Lazy<number>`; documented why `bundles`/`features` tokens cannot defer (declaration-time wiring).
|
|
138
|
+
- `@arki/contracts`: `browser` exports condition serves a zod-only build (no `drizzle-orm`) to client bundlers; table wrappers are node-only and fail loudly at bundle time from browser code.
|
|
139
|
+
- `@arki/db` / `@arki/event-sourcing`: republished to align the `@arki/dot` peer range with 0.4.0.
|
|
140
|
+
|
|
141
|
+
- Updated dependencies
|
|
142
|
+
- Updated dependencies
|
|
143
|
+
- @arki/dot@0.4.0
|
|
144
|
+
- @arki/ts@0.0.3
|
|
145
|
+
|
|
146
|
+
## 0.0.6
|
|
147
|
+
|
|
148
|
+
### Patch Changes
|
|
149
|
+
|
|
150
|
+
- Updated dependencies [4b1c59f]
|
|
151
|
+
- @arki/dot@0.3.0
|
|
152
|
+
|
|
153
|
+
## 0.0.5
|
|
154
|
+
|
|
155
|
+
### Patch Changes
|
|
156
|
+
|
|
157
|
+
- Updated dependencies [48a8733]
|
|
158
|
+
- @arki/dot@0.2.0
|
|
159
|
+
|
|
160
|
+
## 0.0.4
|
|
161
|
+
|
|
162
|
+
### Patch Changes
|
|
163
|
+
|
|
164
|
+
- Updated dependencies [9871fce]
|
|
165
|
+
- @arki/dot@0.1.3
|
|
166
|
+
|
|
167
|
+
## 0.0.3
|
|
168
|
+
|
|
169
|
+
### Patch Changes
|
|
170
|
+
|
|
171
|
+
- Ship TypeScript source in the npm tarball (`src/**` added to `files`). npm installs and the read-only GitHub mirrors now carry the original `.ts` source alongside compiled `dist/` — better debuggability on the experimental track. No runtime changes.
|
|
172
|
+
- Updated dependencies
|
|
173
|
+
- @arki/dot@0.1.2
|
|
174
|
+
|
|
175
|
+
## 0.0.2
|
|
176
|
+
|
|
177
|
+
### Patch Changes
|
|
178
|
+
|
|
179
|
+
- DOT v2 pip contract — typed dependency injection (BREAKING on the experimental track).
|
|
180
|
+
|
|
181
|
+
`pip()` replaces `defineDotPip`: pips declare consumed services as `needs` shapes of type witnesses (`service<T>()` app-local, `token<T>()('key')` cross-package) and hooks destructure them fully typed; provides are inferred from `boot`'s return value. `.use()` is compile-time guarded (unsatisfied needs, type mismatches, wire-key collisions fail at the call site). Declaration order is boot order — the topological sort and name-based `dependencies` are gone; manifest dependency edges are observed from actual wiring. `rename(pip, map, name?)` is the multi-instance primitive, replacing the adapters' silent last-writer-wins `name` options. `lazy(init, { dispose? })` defers expensive opens behind a memoized single-flight `Lazy<T>` handle the kernel auto-disposes; `service.lazy<T>()` lifts eager or lazy providers into a uniform handle. The four `/dot` adapters (`@arki/env`, `@arki/kv`, `@arki/db`, `@arki/event-sourcing`) are migrated to the new contract.
|
|
182
|
+
|
|
183
|
+
- Updated dependencies
|
|
184
|
+
- @arki/dot@0.1.1
|
|
185
|
+
|
|
186
|
+
## 0.0.1
|
|
187
|
+
|
|
188
|
+
### Initial release
|
|
189
|
+
|
|
190
|
+
- First public publication on npm (experimental track) — 2026-05-21.
|
|
191
|
+
|
|
192
|
+
Part of **Wave 1**, the ARKI foundation: zero-dependency utilities and runtime primitives shipped together because every downstream wave (DOT kernel, infrastructure adapters) consumes them. Wave 1 packages: `assert`, `clock`, `contracts`, `date`, `env`, `log`, `resilience`, `slugify`, `string`, `ts`.
|
|
193
|
+
|
|
194
|
+
**Why 0.0.x, not 1.0.0**: ARKI is on an experimental track. The 0.0.x prefix is an honest signal — APIs may change without semver discipline until the kernel is field-tested. The release scorecard (metadata, privacy, dependencies, build-output, packlist, docs, fixtures, DOT gates, agent-native gates) is satisfied at this version; the gate certifies _the artifact_, not _API stability_. Stability is earned through reuse, not declared at first publication.
|
|
195
|
+
|
|
196
|
+
Wave 2 (`@arki/dot`) and Wave 3 (`@arki/db`, `@arki/kv`, `@arki/event-sourcing`) publish in subsequent releases once their inter-package transitive resolution against the public npm registry is verified.
|
package/README.md
CHANGED
|
@@ -1,80 +1,249 @@
|
|
|
1
1
|
# @arki/env
|
|
2
2
|
|
|
3
|
-
Type-safe environment
|
|
3
|
+
Type-safe environment variables for ARKI. `defineEnv()` wraps [`@t3-oss/env-core`](https://env.t3.gg/). It validates on the first read and returns a typed object. Any [Standard Schema](https://standardschema.dev) works: Zod, Valibot, ArkType or a hand-written `~standard` object.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Install
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npm install @arki/env
|
|
9
|
-
# or
|
|
10
8
|
bun add @arki/env
|
|
11
|
-
# or
|
|
12
|
-
pnpm add @arki/env
|
|
13
9
|
```
|
|
14
10
|
|
|
15
|
-
##
|
|
11
|
+
## Quick start
|
|
16
12
|
|
|
17
|
-
|
|
13
|
+
Declare the variables once. Read them anywhere. A bad value throws on the first read, never at import.
|
|
18
14
|
|
|
19
|
-
```ts
|
|
15
|
+
```ts run expect
|
|
20
16
|
import { defineEnv } from '@arki/env/core';
|
|
21
|
-
import {
|
|
17
|
+
import { presets } from '@arki/env/presets';
|
|
22
18
|
|
|
23
|
-
|
|
19
|
+
const env = defineEnv({
|
|
24
20
|
server: {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
DATABASE_URL: contracts.url(),
|
|
28
|
-
LOG_LEVEL: contracts.string('info'),
|
|
21
|
+
PORT: presets.port(3000),
|
|
22
|
+
DATABASE_URL: presets.url(),
|
|
29
23
|
},
|
|
24
|
+
runtimeEnv: { PORT: '8080', DATABASE_URL: 'https://db.example.com' },
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
console.log(env.PORT, typeof env.PORT);
|
|
28
|
+
console.log(env.DATABASE_URL);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
```text
|
|
32
|
+
8080 number
|
|
33
|
+
https://db.example.com
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Leave out `runtimeEnv` in a server app. `defineEnv` then reads `globalThis.process?.env` on the first access.
|
|
37
|
+
|
|
38
|
+
## When not to use this
|
|
39
|
+
|
|
40
|
+
- You need a config file, layered sources or hot reload. This package reads environment variables only.
|
|
41
|
+
- Your validator is asynchronous. Validation is synchronous.
|
|
42
|
+
- You need the secret to stay hidden from code in the same process. `secret()` stops accidents in logs. It is not encryption.
|
|
43
|
+
|
|
44
|
+
## Runtime support
|
|
45
|
+
|
|
46
|
+
`@arki/env/core` and `@arki/env/presets` run on Node, Bun, Deno, browsers and React Native (Hermes). Nothing reads the environment at import. On an engine with no `process`, pass `runtimeEnv`. A browser cannot read server keys, so declare client keys (`clientPrefix`) for code that ships to it. The Dot adapter lives in `@arki/dot/env`. `bun run test:runtimes` runs a smoke test of the built package on each runtime. The Hermes leg needs `HERMES_BIN`.
|
|
47
|
+
|
|
48
|
+
`@arki/env/node` (encrypted env files) runs on Node, Bun and Deno only. It reads files and starts provider CLIs.
|
|
49
|
+
|
|
50
|
+
## Public imports
|
|
51
|
+
|
|
52
|
+
- `@arki/env/core`: `defineEnv`, `secret`, `reveal`, `Redacted`, `describeEnv`, `isSecretSchema`, `formatIssues`.
|
|
53
|
+
- `@arki/env/presets`: `presets` and the per-category namespaces `commonPresets`, `urlPresets`, `parsePresets`.
|
|
54
|
+
- `@arki/env`: re-exports `presets`.
|
|
55
|
+
- `@arki/env/node`: `loadEnvFiles`, `secretValues`, `formatEnvFilesErrors`, `ENV_FILES_ERROR_CODES`, the codec (`encryptValue`, `decryptValue`, `generateKeypair`, `parseEnv`, …), the file edits behind `dot env`, `findPrivateKey` and `DEFAULT_RESOLVERS`.
|
|
56
|
+
- `@arki/env/node/register`: a side-effect import that loads the env files into `process.env`.
|
|
57
|
+
|
|
58
|
+
## Secrets
|
|
59
|
+
|
|
60
|
+
Wrap a secret's schema in `secret(...)`. The variable becomes a `Redacted` value. It prints as `[redacted]` in logs, JSON, template strings and `console.log`. The compiler will not pass it where a string is expected. Read it with `reveal(...)` at the one place that needs it. For an optional secret, call `.optional()` on the result. A missing variable then stays `undefined`.
|
|
61
|
+
|
|
62
|
+
```ts run expect
|
|
63
|
+
import { z } from 'zod';
|
|
64
|
+
|
|
65
|
+
import { defineEnv, reveal, secret } from '@arki/env/core';
|
|
66
|
+
|
|
67
|
+
const env = defineEnv({
|
|
68
|
+
server: { STRIPE_KEY: secret(z.string().min(1)) },
|
|
69
|
+
runtimeEnv: { STRIPE_KEY: 'sk_live_123' },
|
|
30
70
|
});
|
|
31
71
|
|
|
32
|
-
console.log(env.
|
|
33
|
-
console.log(env.
|
|
72
|
+
console.log(String(env.STRIPE_KEY));
|
|
73
|
+
console.log(reveal(env.STRIPE_KEY));
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```text
|
|
77
|
+
[redacted]
|
|
78
|
+
sk_live_123
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
`reveal` also accepts the raw string, because schemas do not run when validation is skipped.
|
|
82
|
+
|
|
83
|
+
## Encrypted env files
|
|
84
|
+
|
|
85
|
+
Commit `.env.<environment>` files with their secrets encrypted. The format is dotenvx 2.x, so the `dotenvx` CLI reads the same files. Each file holds a public key, so anyone can add a value. Only a holder of the private key can read one. The private key lives in `DOTENV_PRIVATE_KEY_<ENV>` (for `.env.production`: `DOTENV_PRIVATE_KEY_PRODUCTION`) or in the gitignored `.env.keys` file.
|
|
86
|
+
|
|
87
|
+
Edit the files with `dot env` from `@arki/dot`:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
dot env init # public key header + private key for .env.development
|
|
91
|
+
dot env set STRIPE_KEY --stdin # stored encrypted; the value never appears in argv
|
|
92
|
+
dot env set PORT 3000 --plain # refused when the schema declares PORT secret()
|
|
93
|
+
dot env run -- bun run dev # load, decrypt, run; secrets redacted when output is piped
|
|
94
|
+
dot env check # CI gate: no tracked key file, no plaintext secret()
|
|
34
95
|
```
|
|
35
96
|
|
|
36
|
-
|
|
97
|
+
Load the files once, at the top of a process. Use `dot env run -- <command>` (the app templates' `with-env` script does), or make `import '@arki/env/node/register'` the first import of an entry. Both write into `process.env`, because package envs such as `@arki/db/env` read it.
|
|
98
|
+
|
|
99
|
+
- The real environment wins. A file never replaces a value that is already set; a different value is reported as shadowed.
|
|
100
|
+
- The environment is `NODE_ENV`, else `development`. Files load from the repository root down to the working directory. A deeper folder wins, and `.env.local` wins over `.env.<environment>`. Under `test`, `.env.local` is skipped.
|
|
101
|
+
- A value can be a reference: `op://vault/item/field`, `infisical://…` or `doppler://…`. The provider's CLI resolves it at load. A public key (`VITE_`, `NEXT_PUBLIC_`, `PUBLIC_`, `EXPO_PUBLIC_`) is never filled from a reference.
|
|
102
|
+
- Keys that change how a process runs (`PATH`, `NODE_OPTIONS`, `LD_*`, TLS settings and more) are never loaded from a file.
|
|
103
|
+
- A private key is needed only when a value needs it. A key already set in the environment needs no decryption.
|
|
104
|
+
- `defineEnv` refuses a value that is still `encrypted:`, so ciphertext never passes as a string.
|
|
37
105
|
|
|
38
|
-
|
|
106
|
+
Mask loaded secrets in logs, wherever they appear:
|
|
39
107
|
|
|
40
|
-
|
|
108
|
+
```ts skip="needs encrypted env files on disk"
|
|
109
|
+
import { secretValues } from '@arki/env/node';
|
|
110
|
+
import { configure, maskSecrets } from '@arki/log';
|
|
41
111
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
- **Parsing**: `csv`, `json`, `keyValue`.
|
|
112
|
+
configure({ processors: [maskSecrets(secretValues)] });
|
|
113
|
+
```
|
|
45
114
|
|
|
46
|
-
|
|
47
|
-
|
|
115
|
+
| Code | Meaning |
|
|
116
|
+
| ------------------------- | --------------------------------------------------------- |
|
|
117
|
+
| `env.missing_private_key` | A file has encrypted values and no private key was found. |
|
|
118
|
+
| `env.decryption_failed` | A value did not decrypt with the key that was found. |
|
|
119
|
+
| `env.reference_failed` | A reference could not be resolved by its provider CLI. |
|
|
120
|
+
|
|
121
|
+
Messages name the file and the key, never a value. `loadEnvFiles` returns `EnvFilesFailed` with these entries in `data.errors`; native file-read problems use `env.file_read_failed`. No entries are applied when loading fails. Raw provider errors and stderr stay in the failure's private cause.
|
|
122
|
+
|
|
123
|
+
### Node file API
|
|
124
|
+
|
|
125
|
+
These come from `@arki/env/node`. The codec functions work on text and never touch the disk. The file functions read and write, and return a failure (check `isFailure`) instead of throwing.
|
|
126
|
+
|
|
127
|
+
| Export | What it does and when to use it |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| `encryptEnvFile(file, keys?)` | Encrypts plaintext values in place, for the listed keys or every value. Returns the keys it encrypted. Use it to import an old plain file. Returns `MissingPublicKey` when the file has no header. |
|
|
130
|
+
| `decryptEnvFile(file, privateKeys, keys?)` | Writes values back as plaintext, for the listed keys or every value. Use it for a one-off migration, never on a tracked file you plan to commit. |
|
|
131
|
+
| `rotateEnvFiles(...)` | Gives every file of one environment a new keypair and encrypts it again. It decrypts everything first, so a wrong key changes nothing. Returns `RotationIncomplete` if it stops half way. |
|
|
132
|
+
| `publicKeyIn(file)` | Reads the public key from the file header, or `undefined` when there is none. Use it to check that a file was initialised. |
|
|
133
|
+
| `envHeader(fileName, publicKey)` | Builds the dotenvx header text (banner, public key, file line). `initEnvFile` uses it; call it to write a header yourself. |
|
|
134
|
+
| `keyNamesFor(fileName)` | Gives the key variable names for a file: `.env.production` gives `DOTENV_PUBLIC_KEY_PRODUCTION` and `DOTENV_PRIVATE_KEY_PRODUCTION`. |
|
|
135
|
+
| `isKeyName(key)` | True for `DOTENV_PUBLIC_KEY*` and `DOTENV_PRIVATE_KEY*`. Use it to skip key lines when you list app values. |
|
|
136
|
+
| `isEncrypted(value)` | True for a value that starts with `encrypted:`. Use it to tell ciphertext from plaintext. |
|
|
137
|
+
| `envValues(document)` | Every key of a parsed file with its last value, in first-seen order. |
|
|
138
|
+
| `readEnvValue(document, key)` | The last value of one key in a parsed file, or `undefined`. |
|
|
139
|
+
| `setEnvValue(text, key, value)` | Returns the text with the key set. It rewrites an existing line in place and appends a new key. Other lines stay as written. |
|
|
140
|
+
| `removeEnvValue(text, key)` | Returns the text without any line that sets the key. |
|
|
141
|
+
| `keysFileCandidates(file, root)` | The `.env.keys` paths searched for a file, nearest first. |
|
|
142
|
+
| `findRoot(cwd)` | The nearest folder above `cwd` that has `.git`, else one with a `workspaces` package.json, else `cwd`. This is where file loading stops. |
|
|
143
|
+
| `envDirs(root, cwd)` | The folders from `root` down to `cwd`, root first. Files load in this order, so a deeper folder wins. |
|
|
144
|
+
| `referenceScheme(value, resolvers)` | Returns the scheme (`op`, `infisical`, `doppler`) when the value is a registered reference, else `undefined`. Unknown URLs stay plain values. |
|
|
145
|
+
| `envPublicKeySchema`, `envPrivateKeySchema` | Standard Schemas that check a secp256k1 key and brand it as `EnvPublicKey` or `EnvPrivateKey`. They never repeat the key in an issue. `parsePublicKey` and `parsePrivateKey` call them. |
|
|
146
|
+
|
|
147
|
+
### Node failures
|
|
148
|
+
|
|
149
|
+
| Failure | Code | When it is returned | How to fix it |
|
|
150
|
+
| --- | --- | --- | --- |
|
|
151
|
+
| `MissingPublicKey` | `env.missing_public_key` | You tried to encrypt into a file that has no public key header. `data.file` names it. | Initialise the file first (`dot env init`, or `initEnvFile`). |
|
|
152
|
+
| `MissingPrivateKey` | `env.missing_private_key` | A file has encrypted values and no private key is available. `data.file` names it. | Restore the key from its backup: set `DOTENV_PRIVATE_KEY_<ENV>` or restore `.env.keys`. |
|
|
153
|
+
| `ReferenceFailed` | `env.reference_failed` | A provider CLI (`op`, `infisical`, `doppler`) rejected a read. `data.scheme` names the provider. HTTP status 502. | Check that the CLI is installed and signed in, and that the reference is right. The raw cause stays private. |
|
|
154
|
+
|
|
155
|
+
## Presets
|
|
156
|
+
|
|
157
|
+
The `presets` namespace holds common variable shapes:
|
|
158
|
+
|
|
159
|
+
- Common: `string`, `number`, `boolean`, `port`, `nodeEnv`, `logLevel`.
|
|
160
|
+
- URLs: `url`, `databaseUrl`, `redisUrl`.
|
|
161
|
+
- Parsing: `json(schema)`, `csvArray(itemSchema)`, `csvSet(itemSchema)`.
|
|
162
|
+
|
|
163
|
+
```ts run expect
|
|
164
|
+
import { z } from 'zod';
|
|
165
|
+
|
|
166
|
+
import { defineEnv } from '@arki/env/core';
|
|
167
|
+
import { presets } from '@arki/env/presets';
|
|
48
168
|
|
|
49
169
|
const env = defineEnv({
|
|
50
170
|
server: {
|
|
51
|
-
ALLOWED_ORIGINS:
|
|
52
|
-
|
|
53
|
-
SERVICE_TAGS: contracts.keyValue(), // 'a=1,b=2' → { a: '1', b: '2' }
|
|
171
|
+
ALLOWED_ORIGINS: presets.csvArray(z.string()),
|
|
172
|
+
FEATURES: presets.json(z.object({ beta: z.boolean() })),
|
|
54
173
|
},
|
|
174
|
+
runtimeEnv: { ALLOWED_ORIGINS: 'a,b,c', FEATURES: '{"beta":true}' },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
console.log(env.ALLOWED_ORIGINS);
|
|
178
|
+
console.log(env.FEATURES);
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```text
|
|
182
|
+
[ "a", "b", "c" ]
|
|
183
|
+
{
|
|
184
|
+
beta: true,
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Failure behaviour
|
|
189
|
+
|
|
190
|
+
`defineEnv` throws an `Error` on the first read of a bad environment. The message starts with your `name` and lists one `path: message` line per issue. The thrown error is an `UnexpectedFailure` that carries the `InvalidEnvironment` failure (code `env.invalid_environment`, exported from `@arki/env/core`), so a catcher can match on the code. Set `options.onValidationError` to throw something else.
|
|
191
|
+
|
|
192
|
+
```ts run expect
|
|
193
|
+
import { defineEnv } from '@arki/env/core';
|
|
194
|
+
import { presets } from '@arki/env/presets';
|
|
195
|
+
|
|
196
|
+
const env = defineEnv({
|
|
197
|
+
server: { PORT: presets.port(), DATABASE_URL: presets.url() },
|
|
198
|
+
runtimeEnv: { PORT: 'abc' },
|
|
55
199
|
});
|
|
200
|
+
|
|
201
|
+
try {
|
|
202
|
+
console.log(env.PORT);
|
|
203
|
+
} catch (error) {
|
|
204
|
+
console.log(error instanceof Error ? 'invalid environment' : 'unknown');
|
|
205
|
+
}
|
|
56
206
|
```
|
|
57
207
|
|
|
58
|
-
|
|
208
|
+
```text
|
|
209
|
+
invalid environment
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The Dot adapter (`env()` from `@arki/dot/env`) validates at boot. A bad environment stops `boot()` with `ARKI_ENV_PLUGIN_E001`. Its `remediation` tells you to set the listed variables. `dot explain` never validates.
|
|
213
|
+
|
|
214
|
+
Handle it by fixing the variables. Do not catch the error and continue with defaults.
|
|
215
|
+
|
|
216
|
+
Node execution is a separate contract. Codec, filesystem and provider operations return exact canonical failures from `@arki/failure`; check `isFailure(result)` before using a result. Key generation returns a keypair or `KeyGenerationFailed`; encryption returns text or `EncryptionFailed`; decryption returns text or `DecryptionFailed`; an unrepresentable dotenv value returns `UnrepresentableValue`. File operations return `FileReadFailed`, `FileWriteFailed` and their precise missing-key/crypto outcomes. Native errors remain private causes, never public messages.
|
|
59
217
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
218
|
+
Parse external keys with `parsePublicKey` / `parsePrivateKey`. Their schemas create distinct `EnvPublicKey` and `EnvPrivateKey` types; a private key cannot be passed where a public key is required. Invalid keys return canonical schema failures without echoing their bytes.
|
|
219
|
+
|
|
220
|
+
`loadEnvFiles({ signal, timeoutMs })` returns a report, `EnvFilesFailed`, `EnvCancelled` or `EnvDeadline`. Its default 30-second budget is shared by all reference reads. Synchronous provider callbacks receive the remaining budget and signal; cancellation is checked between calls. A synchronous syscall cannot be interrupted by a JavaScript abort handler while the event loop is blocked. Native provider subprocesses enforce their remaining timeout. `register` bridges these outcomes to a boot exception by explicit import.
|
|
221
|
+
|
|
222
|
+
Rotation first atomically stores a comma-separated list containing the new and previous private keys in the root and every existing candidate local key store. Staging all stores finishes before ciphertext changes, preserving nearest-store precedence. This is the existing dotenvx multi-key format. It then replaces and reads back every env file before retiring the previous keys. `RotationIncomplete` identifies the recovery key store and committed count; keep that store and retry with it. Both generations remain usable after a partial rotation. Writes use exclusive temp files plus rename; this protects process interruption, not a power-loss durability guarantee.
|
|
223
|
+
|
|
224
|
+
Owned node loads/edits, filesystem reads/writes and provider reads emit default OpenTelemetry spans and duration metrics through the API. No SDK is installed and no export I/O occurs without application configuration. Attributes contain bounded operation/outcome labels; paths, references, key bytes, values and raw causes are excluded. Pure definitions, schemas and local text/crypto transforms need no provider span.
|
|
225
|
+
|
|
226
|
+
## Limits
|
|
227
|
+
|
|
228
|
+
- Validation runs in every environment, production, CI and tests included. `NODE_ENV`, `CI` and `lint` never skip it.
|
|
229
|
+
- The only implicit opt-out is `SKIP_ENV_VALIDATION=1`, exactly `1`. Use it only in build stages that run without secrets. `SKIP_ENV_VALIDATION=true` does not skip.
|
|
230
|
+
- `options.skipValidation` overrides the variable in code.
|
|
231
|
+
- Validation is synchronous. Validation is lazy, so a bad value shows at the first read, not at import.
|
|
232
|
+
- With validation skipped, schemas do not run. Values are raw strings. `reveal` still works on them.
|
|
67
233
|
|
|
68
234
|
## Documentation
|
|
69
235
|
|
|
70
|
-
|
|
71
|
-
[
|
|
236
|
+
- [Agent guide](docs/agent-guide.md): tasks and rules for agents.
|
|
237
|
+
- [Upgrade 0.0.11 to 0.1.0](docs/upgrades/0.0.11-to-0.1.0.md): what broke and how to migrate.
|
|
238
|
+
- [Upgrade 0.1.0 to 0.2.0](docs/upgrades/0.1.0-to-0.2.0.md): the Dot adapter moved to `@arki/dot/env`.
|
|
239
|
+
- The Dot adapter `@arki/dot/env` publishes `env` through `provides`. Consumers write `needs: { env: envPlugin.provides.env }`. See the [`@arki/dot`](https://www.npmjs.com/package/@arki/dot) package for plugin authoring and lifecycle.
|
|
240
|
+
|
|
241
|
+
## Support
|
|
72
242
|
|
|
73
|
-
|
|
74
|
-
package for plugin authoring, lifecycle, diagnostics, and the
|
|
75
|
-
[adapter authoring guide](https://github.com/arkijs/arki/blob/main/packages/dot/docs/adapter-authoring.md).
|
|
243
|
+
Report bugs at [github.com/arki-labs/arki/issues](https://github.com/arki-labs/arki/issues).
|
|
76
244
|
|
|
77
245
|
<!-- bench:start -->
|
|
246
|
+
|
|
78
247
|
## Benchmarks
|
|
79
248
|
|
|
80
249
|
Defining and validating a 15-variable server environment with `defineEnv()`, against `@t3-oss/env-core` (which it wraps), envalid, and a plain zod object parse.
|
|
@@ -85,39 +254,39 @@ Node v22.13.1 on Apple M2 Max, 2026-10-13. Median time per operation, lower is b
|
|
|
85
254
|
|
|
86
255
|
Each call builds a fresh environment from scratch, validates every variable, then reads `PORT`.
|
|
87
256
|
|
|
88
|
-
| Library
|
|
89
|
-
|
|
|
90
|
-
| @arki/env defineEnv()
|
|
91
|
-
| @t3-oss/env-core createEnv() (used directly) | 306.02 µs |
|
|
92
|
-
| envalid cleanEnv()
|
|
93
|
-
| zod z.object().parse(process.env)
|
|
257
|
+
| Library | Time / op | vs fastest |
|
|
258
|
+
| -------------------------------------------- | --------: | ---------: |
|
|
259
|
+
| @arki/env defineEnv() | 25.03 µs | fastest |
|
|
260
|
+
| @t3-oss/env-core createEnv() (used directly) | 306.02 µs | 12.22× |
|
|
261
|
+
| envalid cleanEnv() | 59.23 µs | 2.37× |
|
|
262
|
+
| zod z.object().parse(process.env) | 177.92 µs | 7.11× |
|
|
94
263
|
|
|
95
264
|
### Define only (no read)
|
|
96
265
|
|
|
97
266
|
Declare the environment and never read it. `defineEnv()` defers all work to the first read; the others validate immediately.
|
|
98
267
|
|
|
99
|
-
| Library
|
|
100
|
-
|
|
|
101
|
-
| @arki/env defineEnv() (lazy, no validation yet)
|
|
102
|
-
| @t3-oss/env-core createEnv() (validates now)
|
|
103
|
-
| envalid cleanEnv() (validates now)
|
|
104
|
-
| zod z.object().parse(process.env) (validates now) | 431.26 µs |
|
|
268
|
+
| Library | Time / op | vs fastest |
|
|
269
|
+
| ------------------------------------------------- | --------: | ---------: |
|
|
270
|
+
| @arki/env defineEnv() (lazy, no validation yet) | 126.8 ns | fastest |
|
|
271
|
+
| @t3-oss/env-core createEnv() (validates now) | 519.07 µs | 4094.95× |
|
|
272
|
+
| envalid cleanEnv() (validates now) | 115.66 µs | 912.46× |
|
|
273
|
+
| zod z.object().parse(process.env) (validates now) | 431.26 µs | 3402.26× |
|
|
105
274
|
|
|
106
275
|
### Read one value from an already-validated environment
|
|
107
276
|
|
|
108
277
|
The hot path: `env.PORT` after the environment exists.
|
|
109
278
|
|
|
110
|
-
| Library
|
|
111
|
-
|
|
|
112
|
-
| @arki/env (lazy object over createEnv) |
|
|
113
|
-
| @t3-oss/env-core createEnv()
|
|
114
|
-
| envalid cleanEnv()
|
|
115
|
-
| zod parsed plain object
|
|
279
|
+
| Library | Time / op | vs fastest |
|
|
280
|
+
| -------------------------------------- | --------: | ---------: |
|
|
281
|
+
| @arki/env (lazy object over createEnv) | 229.1 ns | 28.25× |
|
|
282
|
+
| @t3-oss/env-core createEnv() | 50.3 ns | 6.20× |
|
|
283
|
+
| envalid cleanEnv() | 83.8 ns | 10.34× |
|
|
284
|
+
| zod parsed plain object | 8.1 ns | fastest |
|
|
116
285
|
|
|
117
|
-
Read the rows with the
|
|
286
|
+
Read the rows with the presets in mind:
|
|
118
287
|
|
|
119
288
|
- `defineEnv()` is lazy: it builds and validates the environment on the first property read, so "define + first read" is the fair row for it. The "define only" group shows what declaring an env costs before anything reads it.
|
|
120
|
-
- Validation is forced on (`skipValidation: false`) for `defineEnv()` and `createEnv`,
|
|
289
|
+
- Validation is forced on (`skipValidation: false`) for `defineEnv()` and `createEnv`, so the numbers do not depend on `SKIP_ENV_VALIDATION`.
|
|
121
290
|
- `defineEnv()` and `createEnv` share the same zod schemas, so the gap between them is the wrapper alone: building `runtimeEnv` from `process.env`, a labelled error handler, and the lazy object. envalid uses its own validators, and the plain zod parse does no env-specific work (no empty-string handling, no frozen result).
|
|
122
291
|
- `createEnv`, envalid and the zod parse are given the whole `process.env`, as their docs show, so their cost grows with how many variables the process has. `defineEnv()` reads only the keys it declares. The gap therefore depends on the size of the environment it runs in.
|
|
123
292
|
|
|
@@ -126,4 +295,4 @@ Reproduce with `bun run bench` in the package directory; regenerate this section
|
|
|
126
295
|
|
|
127
296
|
## License
|
|
128
297
|
|
|
129
|
-
MIT
|
|
298
|
+
MIT. See [LICENSE](./LICENSE).
|