fix(arkenv): prevent in-place mutation of config objects during coercion#1206
Conversation
|
Run failed. View the logs →
|
🦋 Changeset detectedLatest commit: e8bc799 The changes in this PR will be included in the next version bump. This PR includes changesets to release 7 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
arkenv
@arkenv/build
@arkenv/bun-plugin
@arkenv/cli
@arkenv/fumadocs-ui
@arkenv/nextjs
@arkenv/nuxt
@arkenv/vite-plugin
commit: |
📦 Bundle Size Report
✅ All size limits passed! |
bd132eb to
f07b397
Compare
532df49 to
910dc5e
Compare
There was a problem hiding this comment.
Important
Object.hasOwn breaks TypeScript compilation at the current es2020 target. The old code explicitly handled this with the prototype-based call and a biome suppression comment noting the limitation.
Reviewed changes — this PR refactors applyCoercion from in-place mutation to a non-mutating functional pattern, preventing crashes on frozen/read-only config objects and eliminating side-effects on reusable configs.
- Replace
walkwithupdateAtPath— the new pure function clones objects and arrays along validated paths only when a descendant value changes, otherwise returns the same reference for structural sharing. - Rewrite
coerceValueprimitive-array coercion —.map()replaces the in-placeforloop so frozen arrays are safe. - Add safety and non-mutation tests — covers frozen objects, reference integrity, structural sharing of untouched subtrees, and array coercion without mutation.
- Bump size-limit from 2 kB to 2.1 kB to accommodate the non-mutating coercion logic.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
There was a problem hiding this comment.
✅ No new issues found.
Reviewed changes — the new commit adds "lib": ["es2020", "es2022.object"] to tsconfig.json, resolving the Object.hasOwn TS2550 error. es2022.object is the minimal lib needed — it provides Object.hasOwn without pulling in unrelated ES2022 features.
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
…cross packages and improve coercion logic
…cross core, Next.js, and Nuxt packages
…ic records with Dict<string> across packages
…pes using Dict<string> across packages
…t<string> across packages
…t source detection
…rove core coercion logic for pre-parsed values
There was a problem hiding this comment.
Reviewed changes — since the last approved review, this PR expanded to include Dict<string> type standardization across all packages and a substantial Nuxt runtimeEnv refactor.
- Standardized environment variable types to
Dict<string>— replacedRecord<string, string | undefined>andRecord<string, unknown>withDict<string>in corecreate-env.ts,parse-standard.ts,infer-type.ts, and all Next.js/Nuxt plugin files - Removed
runtimeEnvoption from NuxtcreateEnv— replaced withsourceEnvauto-detection fromprocess.envand Nuxt'sglobalConfig; removedskipDestructureCheckvalidation; removedruntimeEnvfrom all three type-safety overloads (client.ts,server.ts,shared.ts) - Removed
String()andas stringcasts in Next.jscreateEnvInternal—runtimeEnv,extendedEnvValues, andcombinedEnvare now typed asDict<string>, making explicit string conversion redundant - Removed
runtimeEnvfrom Nuxt legacy detection —isLegacynow only checks for"client"or"shared"in the options, notruntimeEnv - Widened
stripEmptyStringssignature — fromRecord<string, string | undefined>toRecord<string, unknown> - Bumped size limit — from 2 kB to 2.1 kB in
packages/arkenv/package.json
⚠️ Nuxt runtimeEnv removal is a breaking change with stale tests
The runtimeEnv option was removed from all three Nuxt type-safety overloads (client.ts, server.ts, shared.ts) and from the createEnvInternal implementation. Callers that were explicitly passing runtimeEnv to Nuxt's createEnv will get type errors and the provided runtimeEnv values are silently ignored (replaced by sourceEnv auto-detection).
The Nuxt test suite still references runtimeEnv in every test case (packages/nuxt/src/index.test.ts lines 10, 25, 43, 61, 82, 111, 159) — these tests are now stale and will fail because runtimeEnv` is no longer read from the options object.
The changeset (.changeset/fix-coercion-in-place-mutation.md) only covers the coercion fix — there is no changeset for this breaking change.
Technical details
# Nuxt `runtimeEnv` removal — breaking change and stale tests
## Affected sites
- `packages/nuxt/src/client.ts:24-26` — `runtimeEnv` removed from 1st overload signature
- `packages/nuxt/src/client.ts:37-38` — `runtimeEnv` removed from 2nd overload signature
- `packages/nuxt/src/client.ts:47` — `isLegacy` check no longer considers `runtimeEnv`
- `packages/nuxt/src/server.ts:23,35` — `runtimeEnv` removed from both overloads
- `packages/nuxt/src/shared.ts:22` — `runtimeEnv` removed from overload
- `packages/nuxt/src/create-env.ts:34-37` — `sourceEnv` auto-detection replaces `runtimeEnv`
- `packages/nuxt/src/create-env.ts:107-110` — extend loop reads from `sourceEnv` instead of `runtimeEnv`
- `packages/nuxt/src/create-env.ts:171-174` — final combinedEnv reads from `sourceEnv`
- `packages/nuxt/src/index.test.ts:10,25,43,61,82,111,159` — tests still pass `runtimeEnv`
## Required outcome
- Add a changeset documenting the `runtimeEnv` removal as a breaking change for `@arkenv/nuxt`
- Update the Nuxt test suite to use `sourceEnv`-compatible test data (e.g., mutate `process.env` before `createEnv` calls)
- Remove the stale `runtimeEnv` references from test cases
## Suggested approach
The Nuxt module already populates `nuxt.options.runtimeConfig`, so `sourceEnv` auto-detection is the correct direction. Tests should either:
1. Set `process.env` keys before calling `createEnv` to simulate `sourceEnv` detection, or
2. Restore a `runtimeEnv` override parameter for testing (lower priority than `sourceEnv`)
## Open questions for the human
- Is the removal of `runtimeEnv` from Nuxt intended to be a breaking change, or should it be restored as an override option?
- Should the Nuxt entry points expose a testing-only API for injecting runtime env values?DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
…_ for environment variable resolution
There was a problem hiding this comment.
ℹ️ One minor nit — otherwise the changes cleanly address the prior review feedback.
Reviewed changes — since the last review, runtimeEnv was fully removed from the Nuxt adapter and tests were updated to use process.env / window.__NUXT__.
- Removed
runtimeEnvfromserver.tsandshared.tsoverloads — matching the earlierclient.tsremoval, all Nuxt entry points now omitruntimeEnvfrom their type signatures. - Introduced server/client-aware
sourceEnv— on the server, onlyprocess.envis consulted; on the client,window.__NUXT__.config.publictakes priority with aprocess.envfallback. - Removed
skipDestructureCheckvalidation block — the runtime check that verified all client/shared keys were present inruntimeEnvis gone, sincesourceEnvauto-detection replaces it. - Updated Nuxt test suite — all tests now set
process.envkeys or mockwindow.__NUXT__instead of passingruntimeEnv; removed the now-irrelevant "missing key in runtimeEnv" and "fallback to process.env" tests; addedtry/finallywindow restoration in client-side tests.
Note: 1 inline comment(s) dropped because they did not anchor to lines inside the PR diff:
packages/nuxt/src/server.ts:42(RIGHT) — line 42 (RIGHT) is not inside a diff hunk
DeepSeek Pro (free via Pullfrog for OSS) | 𝕏
This PR was opened by the [Changesets release](https://github.com/changesets/action) GitHub action. When you're ready to do a release, you can merge this and the packages will be published to npm automatically. If you're not ready to do a release yet, that's fine, whenever you add more changesets to dev, this PR will be updated. # Releases ## @arkenv/nextjs@0.1.0 ### Minor Changes - #### Enforce strict intersection typing on `runtimeEnv` and reject legacy configs _[`#1206`](#1206) [`12ed4f3`](12ed4f3) [@yamcodes](https://github.com/yamcodes)_ Restored strict intersection types (`Record<RequiredKeys, unknown> & Record<string, unknown>`) on the Next.js `createEnv` adapter to guarantee compile-time enforcement of required schema keys. Additionally, narrowed the accepted `runtimeEnv` record value type to `string | undefined` to actively reject invalid configurations. **BREAKING CHANGE**: If you were using the legacy Next.js `env` object configuration (e.g., passing a nested object to `runtimeEnv`), or if you were failing to explicitly map all required keys into `runtimeEnv`, your build will now fail with a TypeScript error. You must explicitly map all variables referenced in your schema as `string | undefined`. Usage: ```ts import { createEnv } from "@arkenv/nextjs"; export const env = createEnv({ client: { NEXT_PUBLIC_API: "string" }, runtimeEnv: { // TypeScript will error if NEXT_PUBLIC_API is missing, // and will also error if you try to pass an object or array. NEXT_PUBLIC_API: process.env.NEXT_PUBLIC_API, }, }); ``` ### Patch Changes <details><summary>Updated 1 dependency</summary> <small> [`a3e32db`](a3e32db) [`12ed4f3`](12ed4f3) [`12ed4f3`](12ed4f3) </small> - `arkenv@0.12.2` </details> ## arkenv@0.12.2 ### Patch Changes - #### Expose `getSchemaKeys` helper _[`#1191`](#1191) [`a3e32db`](a3e32db) [@yamcodes](https://github.com/yamcodes)_ Expose `getSchemaKeys` helper and widen `RuntimeEnvironment` type to support typed framework runtime configurations. - #### Handle pre-parsed primitives gracefully in coercion logic _[`#1206`](#1206) [`12ed4f3`](12ed4f3) [@yamcodes](https://github.com/yamcodes)_ Updated the core coercion logic to defensively skip values that are not strings. This ensures ArkEnv won't crash or behave unpredictably when passed configuration objects that have already been parsed into numbers, booleans, or complex objects by other systems (such as Nuxt's `runtimeConfig`). - #### Fix coercion to prevent in-place mutation of environment config objects _[`#1206`](#1206) [`12ed4f3`](12ed4f3) [@yamcodes](https://github.com/yamcodes)_ Refactor `applyCoercion` to perform non-mutating updates on environment configuration data. Clone objects and arrays along the validation path instead of modifying them in-place, preventing runtime crashes on frozen configuration objects (e.g., framework runtime configurations) and avoiding side-effects on reusable config objects. Usage: ```ts import { createEnv } from "arkenv"; const env = createEnv( { DATABASE: { port: "number" } }, { env: { DATABASE: Object.freeze({ port: "3000" }) } } // Frozen object is now safely parsed without crashes! ); ``` ## @arkenv/build@0.0.1 ### Patch Changes - #### Introduce Nuxt support _[`#1191`](#1191) [`a3e32db`](a3e32db) [@yamcodes](https://github.com/yamcodes)_ Introduce `@arkenv/nuxt` integration package providing a Nuxt module for automatic environment variable validation and runtimeConfig mapping, and add Nuxt support to the CLI scaffold wizard. The Nuxt adapter elegantly embraces Nuxt's native configuration exposure by automatically reading from `window.__NUXT__.config.public`, eliminating the need for developers to manually pass a `runtimeEnv` or `useRuntimeConfig()` map. ## @arkenv/bun-plugin@0.1.10 ### Patch Changes <details><summary>Updated 1 dependency</summary> <small> [`a3e32db`](a3e32db) [`12ed4f3`](12ed4f3) [`12ed4f3`](12ed4f3) </small> - `arkenv@0.12.2` </details> ## @arkenv/cli@0.2.11 ### Patch Changes - #### Introduce Nuxt support _[`#1191`](#1191) [`a3e32db`](a3e32db) [@yamcodes](https://github.com/yamcodes)_ Introduce `@arkenv/nuxt` integration package providing a Nuxt module for automatic environment variable validation and runtimeConfig mapping, and add Nuxt support to the CLI scaffold wizard. The Nuxt adapter elegantly embraces Nuxt's native configuration exposure by automatically reading from `window.__NUXT__.config.public`, eliminating the need for developers to manually pass a `runtimeEnv` or `useRuntimeConfig()` map. ## @arkenv/nuxt@0.0.1 ### Patch Changes - #### Introduce Nuxt support _[`#1191`](#1191) [`a3e32db`](a3e32db) [@yamcodes](https://github.com/yamcodes)_ Introduce `@arkenv/nuxt` integration package providing a Nuxt module for automatic environment variable validation and runtimeConfig mapping, and add Nuxt support to the CLI scaffold wizard. The Nuxt adapter elegantly embraces Nuxt's native configuration exposure by automatically reading from `window.__NUXT__.config.public`, eliminating the need for developers to manually pass a `runtimeEnv` or `useRuntimeConfig()` map. <details><summary>Updated 2 dependencies</summary> <small> [`a3e32db`](a3e32db) [`12ed4f3`](12ed4f3) [`12ed4f3`](12ed4f3) [`a3e32db`](a3e32db) </small> - `arkenv@0.12.2` - `@arkenv/build@0.0.1` </details> ## @arkenv/vite-plugin@0.1.4 ### Patch Changes <details><summary>Updated 1 dependency</summary> <small> [`a3e32db`](a3e32db) [`12ed4f3`](12ed4f3) [`12ed4f3`](12ed4f3) </small> - `arkenv@0.12.2` </details> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
##### [v0.12.2](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.12.2) ##### Patch Changes - #### Expose `getSchemaKeys` helper *[`#1191`](yamcodes/arkenv#1191) [`a3e32db`](yamcodes/arkenv@a3e32db) [@yamcodes](https://github.com/yamcodes)* Expose `getSchemaKeys` helper and widen `RuntimeEnvironment` type to support typed framework runtime configurations. - #### Handle pre-parsed primitives gracefully in coercion logic *[`#1206`](yamcodes/arkenv#1206) [`12ed4f3`](yamcodes/arkenv@12ed4f3) [@yamcodes](https://github.com/yamcodes)* Updated the core coercion logic to defensively skip values that are not strings. This ensures ArkEnv won't crash or behave unpredictably when passed configuration objects that have already been parsed into numbers, booleans, or complex objects by other systems (such as Nuxt's `runtimeConfig`). - #### Fix coercion to prevent in-place mutation of environment config objects *[`#1206`](yamcodes/arkenv#1206) [`12ed4f3`](yamcodes/arkenv@12ed4f3) [@yamcodes](https://github.com/yamcodes)* Refactor `applyCoercion` to perform non-mutating updates on environment configuration data. Clone objects and arrays along the validation path instead of modifying them in-place, preventing runtime crashes on frozen configuration objects (e.g., framework runtime configurations) and avoiding side-effects on reusable config objects. Usage: ```ts import { createEnv } from "arkenv"; const env = createEnv( { DATABASE: { port: "number" } }, { env: { DATABASE: Object.freeze({ port: "3000" }) } } // Frozen object is now safely parsed without crashes! ); ``` ##### [v0.12.1](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.12.1) ##### Patch Changes - #### Add `emptyAsUndefined` option to treat empty env values as missing *[`#1188`](yamcodes/arkenv#1188) [`3bfbcb7`](yamcodes/arkenv@3bfbcb7) [@yamcodes](https://github.com/yamcodes)* Add a new `emptyAsUndefined` configuration option to both `createEnv` (ArkType mode) and `arkenv/standard` (Standard Schema mode). When enabled, environment variables set to empty strings (e.g., `PORT=` in a `.env` file) are treated as if they were missing, allowing defaults to apply and preventing unnecessary validation errors for numeric, boolean, or array types. **Usage:** ```ts import arkenv from "arkenv"; const env = arkenv( { PORT: "number = 3000", DEBUG: "boolean = false" }, { emptyAsUndefined: true } ); // Given PORT= and DEBUG= in the environment: // env.PORT → 3000 // env.DEBUG → false ``` ```ts import arkenv from "arkenv/standard"; import { z } from "zod"; const env = arkenv( { PORT: z.coerce.number().default(3000) }, { emptyAsUndefined: true } ); ``` - The default behavior remains unchanged (`emptyAsUndefined: false`). - Keys with empty values are removed from the input record before validation so that ArkType defaults and optional types work correctly. ##### [v0.12.0](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.12.0) ##### Minor Changes - #### Add Standard JSON Schema coercion to `arkenv/standard` *[`#1154`](yamcodes/arkenv#1154) [`88b0eee`](yamcodes/arkenv@88b0eee) [@yamcodes](https://github.com/yamcodes)* Introduce opt-out type coercion for standard mode (`arkenv/standard`). This coercion only works if the validator is a standard JSON Schema compliant validator (e.g., Zod, Valibot, or custom schemas that implement the `StandardJSONSchemaV1` interface). This automatically enables coercion for environment variables without relying on ArkType's runtime footprint. If you need to disable coercion, explicitly pass `{ coerce: false }` in your configuration: ```ts import arkenv from "arkenv/standard"; import { z } from "zod"; const env = arkenv({ PORT: z.number() }, { coerce: false }); ``` BREAKING CHANGE: Coercion is now enabled by default in `arkenv/standard`. This will automatically coerce environment variables to their expected types (e.g., strings containing numbers, booleans, or dates will be converted to their respective types) based on the JSON Schema of your validators. It's unlikely to affect you unless you were relying on validation failing for uncoerced string inputs, or have custom schemas that expect raw strings instead of coerced values. ##### [v0.11.1](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.11.1) ##### Patch Changes - #### Add `Infer<T>` helper to resolve environment variable types *[`#1092`](yamcodes/arkenv#1092) [`c6c30ab`](yamcodes/arkenv@c6c30ab) [@yamcodes](https://github.com/yamcodes)* Introduce the `Infer<T>` type helper, allowing developers to extract the inferred output types of their environment schemas. It supports both declarative schema shapes and compiled schemas (like Zod or ArkType types). Usage: ```ts import { createEnv, type Infer } from "arkenv"; import { type } from "arktype"; const schema = { PORT: type.number, }; export type Env = Infer<typeof schema>; ``` ##### [v0.11.0](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.11.0) ##### Minor Changes - #### Remove `ArkEnvError` import from "arkenv" *[`#815`](yamcodes/arkenv#815) [`5e8025f`](yamcodes/arkenv@5e8025f) [@yamcodes](https://github.com/yamcodes)* The `ArkEnvError` class is now only available via: ```ts import { ArkEnvError } from "arkenv/core"; ``` ##### [v0.10.0](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.10.0) ##### Minor Changes - #### `arkenv/standard` import *[`#806`](yamcodes/arkenv#806) [`f9010d0`](yamcodes/arkenv@f9010d0) [@yamcodes](https://github.com/yamcodes)* `arkenv` now ships three separate entry points: - **`arkenv`** (main): ArkType-first. Includes `createEnv` (aliased to `arkenv` default import), `type`, and `ArkEnvError`. Importing from this entry requires you to have ArkType installed. - **`arkenv/standard`**: ArkType-free. A standalone `createEnv` (aliased to `arkenv` default import) for Standard Schema validators (Zod, Valibot, etc.), not requiring ArkType. - **`arkenv/core`**: Mode-agnostic primitives - `ArkEnvError` and `ValidationIssue`. ##### Breaking changes ```` **1. `validator: "standard"` option removed; `arkenv` now statically requires ArkType.** The `validator` config option has been removed - ArkType is now always required when importing from `arkenv`. For a zero-ArkType bundle, use `arkenv/standard`: ```ts // ❌ Before import arkenv from "arkenv"; import { z } from "zod"; const env = arkenv({ PORT: z.coerce.number() }, { validator: "standard" }); // ✅ After import arkenv from "arkenv/standard"; import { z } from "zod"; const env = arkenv({ PORT: z.coerce.number() }); ``` **2. `type` moved from `arkenv/arktype` to `arkenv`.** The `type` helper is now exported from the main entry. The `arkenv/arktype` sub-path is no longer public: ```ts // ❌ Before import { type } from "arkenv/arktype"; // ✅ After import { type } from "arkenv"; // 'type' is the ArkEnv helper, not a TS type modifier ``` ````
##### [v0.12.3](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.12.3) ##### Patch Changes - #### Improve npm keywords across published packages for discoverability *[`#1383`](yamcodes/arkenv#1383) [`bf60ab2`](yamcodes/arkenv@bf60ab2) [@yamcodes](https://github.com/yamcodes)* Clean up and extend the `keywords` field of every published package so npm search, aggregators, and LLM-powered package discovery surface ArkEnv for the terms users actually search for. - Remove the misleading `pnpm` keyword from `arkenv` and add `env`, `environment-variables`, `dotenv`, `config`, `standard-schema`, and the supported validators `zod` and `valibot`. - Deduplicate the repeated `arkenv` keyword in `@arkenv/vite-plugin`. - Give every env-related package a shared baseline (`env`, `environment-variables`, `dotenv`, `config`, `validation`, `typesafe`, `standard-schema`) alongside their integration-specific terms. - Add a keyword set to `@arkenv/fumadocs-ui`, which previously had none. ##### [v0.12.2](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.12.2) ##### Patch Changes - #### Expose `getSchemaKeys` helper *[`#1191`](yamcodes/arkenv#1191) [`a3e32db`](yamcodes/arkenv@a3e32db) [@yamcodes](https://github.com/yamcodes)* Expose `getSchemaKeys` helper and widen `RuntimeEnvironment` type to support typed framework runtime configurations. - #### Handle pre-parsed primitives gracefully in coercion logic *[`#1206`](yamcodes/arkenv#1206) [`12ed4f3`](yamcodes/arkenv@12ed4f3) [@yamcodes](https://github.com/yamcodes)* Updated the core coercion logic to defensively skip values that are not strings. This ensures ArkEnv won't crash or behave unpredictably when passed configuration objects that have already been parsed into numbers, booleans, or complex objects by other systems (such as Nuxt's `runtimeConfig`). - #### Fix coercion to prevent in-place mutation of environment config objects *[`#1206`](yamcodes/arkenv#1206) [`12ed4f3`](yamcodes/arkenv@12ed4f3) [@yamcodes](https://github.com/yamcodes)* Refactor `applyCoercion` to perform non-mutating updates on environment configuration data. Clone objects and arrays along the validation path instead of modifying them in-place, preventing runtime crashes on frozen configuration objects (e.g., framework runtime configurations) and avoiding side-effects on reusable config objects. Usage: ```ts import { createEnv } from "arkenv"; const env = createEnv( { DATABASE: { port: "number" } }, { env: { DATABASE: Object.freeze({ port: "3000" }) } } // Frozen object is now safely parsed without crashes! ); ``` ##### [v0.12.1](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.12.1) ##### Patch Changes - #### Add `emptyAsUndefined` option to treat empty env values as missing *[`#1188`](yamcodes/arkenv#1188) [`3bfbcb7`](yamcodes/arkenv@3bfbcb7) [@yamcodes](https://github.com/yamcodes)* Add a new `emptyAsUndefined` configuration option to both `createEnv` (ArkType mode) and `arkenv/standard` (Standard Schema mode). When enabled, environment variables set to empty strings (e.g., `PORT=` in a `.env` file) are treated as if they were missing, allowing defaults to apply and preventing unnecessary validation errors for numeric, boolean, or array types. **Usage:** ```ts import arkenv from "arkenv"; const env = arkenv( { PORT: "number = 3000", DEBUG: "boolean = false" }, { emptyAsUndefined: true } ); // Given PORT= and DEBUG= in the environment: // env.PORT → 3000 // env.DEBUG → false ``` ```ts import arkenv from "arkenv/standard"; import { z } from "zod"; const env = arkenv( { PORT: z.coerce.number().default(3000) }, { emptyAsUndefined: true } ); ``` - The default behavior remains unchanged (`emptyAsUndefined: false`). - Keys with empty values are removed from the input record before validation so that ArkType defaults and optional types work correctly. ##### [v0.12.0](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.12.0) ##### Minor Changes - #### Add Standard JSON Schema coercion to `arkenv/standard` *[`#1154`](yamcodes/arkenv#1154) [`88b0eee`](yamcodes/arkenv@88b0eee) [@yamcodes](https://github.com/yamcodes)* Introduce opt-out type coercion for standard mode (`arkenv/standard`). This coercion only works if the validator is a standard JSON Schema compliant validator (e.g., Zod, Valibot, or custom schemas that implement the `StandardJSONSchemaV1` interface). This automatically enables coercion for environment variables without relying on ArkType's runtime footprint. If you need to disable coercion, explicitly pass `{ coerce: false }` in your configuration: ```ts import arkenv from "arkenv/standard"; import { z } from "zod"; const env = arkenv({ PORT: z.number() }, { coerce: false }); ``` BREAKING CHANGE: Coercion is now enabled by default in `arkenv/standard`. This will automatically coerce environment variables to their expected types (e.g., strings containing numbers, booleans, or dates will be converted to their respective types) based on the JSON Schema of your validators. It's unlikely to affect you unless you were relying on validation failing for uncoerced string inputs, or have custom schemas that expect raw strings instead of coerced values. ##### [v0.11.1](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.11.1) ##### Patch Changes - #### Add `Infer<T>` helper to resolve environment variable types *[`#1092`](yamcodes/arkenv#1092) [`c6c30ab`](yamcodes/arkenv@c6c30ab) [@yamcodes](https://github.com/yamcodes)* Introduce the `Infer<T>` type helper, allowing developers to extract the inferred output types of their environment schemas. It supports both declarative schema shapes and compiled schemas (like Zod or ArkType types). Usage: ```ts import { createEnv, type Infer } from "arkenv"; import { type } from "arktype"; const schema = { PORT: type.number, }; export type Env = Infer<typeof schema>; ``` ##### [v0.11.0](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.11.0) ##### Minor Changes - #### Remove `ArkEnvError` import from "arkenv" *[`#815`](yamcodes/arkenv#815) [`5e8025f`](yamcodes/arkenv@5e8025f) [@yamcodes](https://github.com/yamcodes)* The `ArkEnvError` class is now only available via: ```ts import { ArkEnvError } from "arkenv/core"; ``` ##### [v0.10.0](https://github.com/yamcodes/arkenv/releases/tag/arkenv%400.10.0) ##### Minor Changes - #### `arkenv/standard` import *[`#806`](yamcodes/arkenv#806) [`f9010d0`](yamcodes/arkenv@f9010d0) [@yamcodes](https://github.com/yamcodes)* `arkenv` now ships three separate entry points: - **`arkenv`** (main): ArkType-first. Includes `createEnv` (aliased to `arkenv` default import), `type`, and `ArkEnvError`. Importing from this entry requires you to have ArkType installed. - **`arkenv/standard`**: ArkType-free. A standalone `createEnv` (aliased to `arkenv` default import) for Standard Schema validators (Zod, Valibot, etc.), not requiring ArkType. - **`arkenv/core`**: Mode-agnostic primitives - `ArkEnvError` and `ValidationIssue`. ##### Breaking changes ```` **1. `validator: "standard"` option removed; `arkenv` now statically requires ArkType.** The `validator` config option has been removed - ArkType is now always required when importing from `arkenv`. For a zero-ArkType bundle, use `arkenv/standard`: ```ts // ❌ Before import arkenv from "arkenv"; import { z } from "zod"; const env = arkenv({ PORT: z.coerce.number() }, { validator: "standard" }); // ✅ After import arkenv from "arkenv/standard"; import { z } from "zod"; const env = arkenv({ PORT: z.coerce.number() }); ``` **2. `type` moved from `arkenv/arktype` to `arkenv`.** The `type` helper is now exported from the main entry. The `arkenv/arktype` sub-path is no longer public: ```ts // ❌ Before import { type } from "arkenv/arktype"; // ✅ After import { type } from "arkenv"; // 'type' is the ArkEnv helper, not a TS type modifier ``` ````

Description
This PR refactors
applyCoercionto perform non-mutating updates on environment configuration data by recursively cloning/structurally sharing objects and arrays along the validation target path instead of modifying the existing ones in-place.This fixes:
runtimeConfig).Record<string, unknown>environments.Testing Done
Added comprehensive unit tests in
packages/arkenv/src/coercion/shared.test.tsto assert:All 69 test files (701 tests) in the workspace pass successfully.