Skip to content

type Coercion Implementation#565

Merged
yamcodes merged 39 commits intocoercionfrom
228-coercion-stringnumber-stringboolean-etc
Dec 20, 2025
Merged

type Coercion Implementation#565
yamcodes merged 39 commits intocoercionfrom
228-coercion-stringnumber-stringboolean-etc

Conversation

@yamcodes
Copy link
Owner

@yamcodes yamcodes commented Dec 20, 2025

Closes #566

Summary by CodeRabbit

  • New Features

    • Environment values are now auto-coerced from strings into numbers and booleans (including number subtypes like ports and epochs).
  • Breaking Changes

    • The legacy boolean keyword was removed.
    • Within the coercion scope, string inputs coerce to number/boolean; some number constraints (ranges/divisors) do not apply to coerced numbers — see workarounds.
  • Documentation

    • Coercion guide, design, spec, and task docs added.
  • Tests

    • End-to-end coercion integration tests added.

✏️ Tip: You can customize this high-level summary in your review settings.

yamcodes and others added 19 commits November 30, 2025 01:18
@changeset-bot
Copy link

changeset-bot bot commented Dec 20, 2025

🦋 Changeset detected

Latest commit: 8ce91d5

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 6 packages
Name Type
arkenv Minor
@repo/keywords Minor
@repo/scope Patch
@arkenv/bun-plugin Patch
@arkenv/vite-plugin Patch
@repo/types Patch

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

@vercel
Copy link

vercel bot commented Dec 20, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
arkenv Ready Ready Preview, Comment Dec 20, 2025 3:18pm

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Adds scope-level coercion so arkenv accepts string inputs for number (and subtypes) and boolean, converting them to native types during validation. Implements coercion utilities, updates keywords (including port), adds tests, docs, and related spec/design/task files.

Changes

Cohort / File(s) Summary
Changesets
​.changeset/cyan-loops-hear.md, ​.changeset/humble-lizards-judge.md, ​.changeset/plain-webs-dig.md
Adds entries documenting coercion feature and breaking note about boolean keyword removal / scope-based coercion.
Design & Spec
openspec/changes/add-coercion/design.md, openspec/changes/add-coercion/proposal.md, openspec/changes/add-coercion/specs/coercion/spec.md, openspec/changes/add-coercion/tasks.md
New design, proposal, spec, and task checklist describing scope-based string→number/boolean coercion and verification scenarios.
Docs
packages/arkenv/README.md
Adds "Coercion" section explaining behavior and examples.
Examples / env files
apps/playgrounds/node/.env.example, examples/basic/.env.example
Removed NODE_ENV, DEBUG, ZED_ENV lines; retain HOST and PORT.
Example apps
apps/playgrounds/node/index.ts, examples/basic/index.ts
Remove named type import usage and some schema keys; log env properties directly instead of local destructured vars.
Core — Scope & Keywords
packages/internal/scope/src/index.ts, packages/internal/keywords/src/index.ts
Adds coercedNumber and coercedBoolean helpers and wrapModule to apply coercion; wraps number keyword and replaces boolean with coerced variant; updates port keyword to accept `string
ArkEnv implementation
packages/arkenv/src/create-env.ts
Adds isCompiledType guard to detect compiled type schemas and simplifies schema selection prior to validation.
Tests
packages/arkenv/src/coercion.integration.test.ts, packages/arkenv/src/create-env.test.ts
New integration tests for coercion paths and subtype behavior; small update to expected error formatting in an existing test.

Sequence Diagram

sequenceDiagram
    participant App as User/App
    participant Env as arkenv()
    participant Scope as Scope (coerced keywords)
    participant Type as ArkType Validation
    participant Result as Validated Env

    App->>Env: call arkenv(schema) with string env values
    Env->>Env: read raw env (strings)
    Env->>Type: invoke validation(parse) with schema
    Type->>Scope: resolve "number"/"boolean" keywords
    Scope->>Scope: coercedNumber("3000") => 3000
    Scope->>Scope: coercedBoolean("true") => true
    Type->>Type: validate coerced values against constraints
    Type->>Result: return typed env object
    Result->>App: { PORT: 3000, DEBUG: true }
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Review coercion helpers and edge-case handling in packages/internal/scope/src/index.ts.
  • Verify port parsing and range checking in packages/internal/keywords/src/index.ts.
  • Validate integration tests in packages/arkenv/src/coercion.integration.test.ts cover subtype boundaries and failure modes.
  • Confirm isCompiledType behavior in packages/arkenv/src/create-env.ts.

Possibly related issues

Possibly related PRs

Poem

🐇 I nibbled strings and turned them bright,

numbers hummed and booleans took flight,
Scope wrapped snug, keywords all new,
Env values tidy — hopped into view! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning The PR includes changes to example files and node playground that remove unrelated environment variables (NODE_ENV, DEBUG, ZED_ENV), which fall outside the core coercion implementation scope. Remove changes to .env.example files and index.ts files in examples/basic and apps/playgrounds/node that remove variables unrelated to coercion implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Linked Issues check ❓ Inconclusive The linked issue #566 provides minimal context (only labeled 'Implementation'). Based on the changeset and file modifications, the PR implements native coercion support for number and boolean types in arkenv, which aligns with typical coercion implementation requirements. Review the detailed requirements in linked issue #566 to confirm all specified coercion behaviors are implemented, including edge cases and expected error handling.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly describes the main change: implementing type coercion functionality, which is the core focus of this PR across multiple files.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions bot added docs Improvements or additions to documentation arkenv Changes to the `arkenv` npm package. example Issues or Pull Requests concerning at least one ArkEnv example. (Found in the `examples/` directory) tests This issue or PR is about adding, removing or changing tests labels Dec 20, 2025
@pkg-pr-new
Copy link

pkg-pr-new bot commented Dec 20, 2025

Open in StackBlitz

npm i https://pkg.pr.new/arkenv@565
npm i https://pkg.pr.new/@arkenv/bun-plugin@565
npm i https://pkg.pr.new/@arkenv/vite-plugin@565

commit: a98a0c9

@arkenv-bot
Copy link
Contributor

arkenv-bot bot commented Dec 20, 2025

📦 Bundle Size Report

Package Size Limit Diff Status
arkenv 975 B 2 kB +33.9%

All size limits passed!

@yamcodes yamcodes changed the title type Coercion type Coercion Implementation Dec 20, 2025
@yamcodes
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (7)
examples/basic/.env.example (1)

2-2: Add a blank line at the end of the file.

The file is missing a trailing newline, which is a common convention for text files and is expected by formatting tools.

🔎 Proposed fix
 HOST=localhost
 PORT=3000
+
.changeset/humble-lizards-judge.md (1)

7-7: Consider adding a reference to the new coercion documentation.

To help users transition smoothly, consider adding a link to the arkenv coercion documentation or a brief note explaining how the new scope-based coercion works with number and boolean types. This would give users immediate guidance on the recommended migration path.

apps/playgrounds/node/index.ts (1)

1-19: LGTM! Clean implementation of env schema with coercion.

The simplified structure effectively demonstrates arkenv usage with proper built-in validators and default values. The changes remove unnecessary complexity while maintaining type safety.

Based on learnings: Uses built-in validators (string.host, number.port), union types for enums, and follows the ArkType schema pattern.

💡 Optional: Enhance coercion demonstration

Since this PR introduces coercion for number and boolean types, consider adding comments that explicitly highlight this behavior:

 const env = arkenv({
 	HOST: "string.host",
-	PORT: "number.port",
+	PORT: "number.port", // Coerces string "8080" → number 8080
 	NODE_ENV: "'development' | 'production' | 'test' = 'development'",
-	DEBUG: "boolean = true",
+	DEBUG: "boolean = true", // Coerces string "true"/"false" → boolean
 });

This would make the coercion feature more apparent to users exploring the playground.

examples/basic/index.ts (2)

1-19: LGTM! Follows arkenv patterns correctly.

The example demonstrates clean usage of arkenv with appropriate built-in validators and default values. The implementation follows all coding guidelines and leverages ArkType's syntax effectively.

Based on learnings: Uses built-in validators, maintains readable schema syntax, and demonstrates real-world usage patterns.

💡 Optional: Strengthen coercion demonstration for example

Since this PR introduces coercion and this file serves as a basic example, consider making the coercion behavior more explicit:

 // Automatically validate and parse process.env
 // TypeScript knows the ✨exact✨ types!
+// Environment variables (strings) are automatically coerced to target types:
+// - PORT: string "8080" → number 8080
+// - DEBUG: string "true" → boolean true
 console.log({
 	host: env.HOST,
-	port: env.PORT,
+	port: env.PORT, // type: number (coerced from string)
-	nodeEnv: env.NODE_ENV,
+	nodeEnv: env.NODE_ENV, // type: "development" | "production" | "test"
-	debug: env.DEBUG,
+	debug: env.DEBUG, // type: boolean (coerced from string)
 });

Alternatively, add type verification:

console.log({
	host: env.HOST,
	port: env.PORT,
	nodeEnv: env.NODE_ENV,
	debug: env.DEBUG,
	// Demonstrate coercion:
	portType: typeof env.PORT, // "number"
	debugType: typeof env.DEBUG, // "boolean"
});

This would better showcase the coercion feature that is the focus of this PR.


1-19: Note: Code duplication with playground.

This file is identical to apps/playgrounds/node/index.ts. While having separate example and playground files is a common pattern, consider whether they need to remain in sync or if they should demonstrate different aspects of arkenv.

packages/internal/keywords/src/index.ts (1)

6-14: Consider simplifying port to accept only number type.

The port type currently accepts string | number and includes string-to-number parsing logic. However, since packages/internal/scope/src/index.ts (line 77) wraps all number keywords with coercedNumber, string inputs are already coerced to numbers before reaching the port validator. This makes the string handling at line 7 unreachable code when port is used through the $ scope.

🔎 Simplified implementation
 /**
- * A `number` (or a `string` that can be parsed into a `number`) between 0 and 65535
+ * A `number` between 0 and 65535
  */
-export const port = type("string | number", "=>", (data, ctx) => {
-	const asNumber = typeof data === "string" ? Number.parseInt(data, 10) : data;
+export const port = type("number", "=>", (data, ctx) => {
 	const isInteger = Number.isInteger(asNumber);
 	const isBetween = 0 <= asNumber && asNumber <= 65535;
 	if (!isInteger || !isBetween) {
 		ctx.mustBe("an integer between 0 and 65535");
 	}
-	return asNumber;
+	return data;
 });

Note: Only apply this change if port is exclusively used through the wrapped scope. If there are direct usages outside the scope that rely on string input handling, keep the current implementation.

packages/internal/scope/src/index.ts (1)

29-41: Consider supporting additional boolean string variants.

The coercedBoolean implementation only accepts literal "true" and "false" strings (lines 32-33). Common environment variable patterns also use "1"/"0", "yes"/"no", "on"/"off", or case variations like "True"/"FALSE".

The current strict behavior may be intentional for type safety and clarity. However, if broader compatibility with common environment variable conventions is desired, consider extending the coercion logic:

🔎 Extended boolean coercion example
 const coercedBoolean = type("string | boolean")
 	.pipe((s) => {
 		if (typeof s === "boolean") return s;
-		if (s === "true") return true;
-		if (s === "false") return false;
+		const lower = s.toLowerCase();
+		if (lower === "true" || lower === "1" || lower === "yes" || lower === "on") return true;
+		if (lower === "false" || lower === "0" || lower === "no" || lower === "off") return false;
 		return s;
 	})
 	.narrow((data, ctx): data is boolean => {
 		if (typeof data !== "boolean") {
 			return ctx.mustBe("a boolean");
 		}
 		return true;
 	});

If the strict behavior is preferred, consider documenting this decision to guide users on expected boolean string formats.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6ac9f2d and 7b984be.

⛔ Files ignored due to path filters (2)
  • examples/with-bun-react/bun.lock is excluded by !**/*.lock
  • examples/with-bun/bun.lock is excluded by !**/*.lock
📒 Files selected for processing (17)
  • .changeset/cyan-loops-hear.md (1 hunks)
  • .changeset/humble-lizards-judge.md (1 hunks)
  • .changeset/plain-webs-dig.md (1 hunks)
  • apps/playgrounds/node/.env.example (1 hunks)
  • apps/playgrounds/node/index.ts (1 hunks)
  • examples/basic/.env.example (1 hunks)
  • examples/basic/index.ts (1 hunks)
  • openspec/changes/add-coercion/design.md (1 hunks)
  • openspec/changes/add-coercion/proposal.md (1 hunks)
  • openspec/changes/add-coercion/specs/coercion/spec.md (1 hunks)
  • openspec/changes/add-coercion/tasks.md (1 hunks)
  • packages/arkenv/README.md (1 hunks)
  • packages/arkenv/src/coercion.integration.test.ts (1 hunks)
  • packages/arkenv/src/create-env.test.ts (1 hunks)
  • packages/arkenv/src/create-env.ts (1 hunks)
  • packages/internal/keywords/src/index.ts (1 hunks)
  • packages/internal/scope/src/index.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (14)
packages/arkenv/**/*.ts

📄 CodeRabbit inference engine (.cursor/rules/arktype.mdc)

packages/arkenv/**/*.ts: Use ArkType's type() function to define schemas in environment variable definitions
Leverage ArkType's type inference for TypeScript types instead of manual type definitions
Use the scoped $ type system for custom types defined in scope.ts
Keep environment variable schemas readable and TypeScript-like using ArkType syntax
Use union types for enums in ArkType schemas (e.g., "'dev' | 'prod'") instead of separate enum definitions
Leverage ArkType's built-in types (e.g., string.host, number.port) where possible in environment schemas
Convert ArkType validation errors to ArkEnvError for user-friendly error messages that include variable name and expected type

Files:

  • packages/arkenv/src/coercion.integration.test.ts
  • packages/arkenv/src/create-env.test.ts
  • packages/arkenv/src/create-env.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

**/*.{ts,tsx}: Prefer type over interface for type definitions in TypeScript
Use TypeScript 5.1+ features when appropriate
Leverage const type parameters for better inference in TypeScript
Use JSDoc comments for public APIs
Use tabs for indentation (configured in Biome)
Use double quotes for strings (configured in Biome)
Organize imports automatically (Biome handles this)
Avoid explicit types when TypeScript can infer them (noInferrableTypes error)
Use as const where appropriate for immutable values (useAsConstAssertion error)
Don't reassign function parameters (noParameterAssign error)
Place default parameters last in function signatures (useDefaultParameterLast error)
Always initialize enum values (useEnumInitializers error)
Declare one variable per statement (useSingleVarDeclarator error)
Avoid unnecessary template literals (noUnusedTemplateLiteral error)
Prefer Number.parseInt over global parseInt (useNumberNamespace error)
Use kebab-case for TypeScript filenames (e.g., create-env.ts)
Use camelCase for function names (e.g., createEnv)
Use PascalCase for type names (e.g., ArkEnvError)
Use UPPER_SNAKE_CASE for environment variables and constants
Include examples in JSDoc comments when helpful for public APIs
Document complex type logic with JSDoc comments
Use ArkEnvError for environment variable validation errors
Provide clear, actionable error messages that include the variable name and expected type

**/*.{ts,tsx}: Use createEnv(schema) as the main function for validated environment objects, available as the default export
Use built-in validators (host, port, url, email) from src/types.ts when available instead of custom validation
Use ArkEnvError for environment variable errors, not generic errors
Environment schema definitions should use built-in validators, ArkType string literals, and support default values in the schema pattern
Use logical grouping for related environment variables in schemas
Use descriptive env...

Files:

  • packages/arkenv/src/coercion.integration.test.ts
  • packages/arkenv/src/create-env.test.ts
  • packages/arkenv/src/create-env.ts
  • packages/internal/keywords/src/index.ts
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

Co-locate tests with components: Component.tsx next to Component.test.tsx

**/*.test.{ts,tsx}: Test individual functions, components, and hooks in isolation with mocked dependencies using the *.test.ts or *.test.tsx suffix
Mock process.env in tests for different scenarios and save/restore original env in beforeEach/afterEach hooks
Use Vitest's describe/it structure in test files
Test edge cases including invalid and missing environment variable values
Use Vitest for testing framework

**/*.test.{ts,tsx}: Use Vitest for unit and integration tests
Test individual functions, components, and hooks in isolation with mocked dependencies in unit tests
Unit tests should focus on individual function logic and edge cases, component rendering and props, error handling and validation, and type checking
Unit tests should execute in less than 100ms per test
Mock external dependencies (clipboard, network, etc.) in unit tests
Co-locate unit test files with source files using naming convention: source file → test file (e.g., create-env.ts → create-env.test.ts)
Test component behavior, not aesthetics, and focus on what users can do and what the component guarantees through its API
Test component public API (props, events, and component contract), user behavior (clicks, typing, focus, keyboard, ARIA), state transitions, accessibility, and side effects in component tests
Do not test pure styling or CSS classes, library internals (Radix/shadcn), implementation details (hooks, setState, private variables), or visual variants in component tests
Use Testing Library with user-event for real user simulation in component tests
Query by role, name, label, and text (accessibility first) in component tests
Use beforeEach/afterEach for cleanup, not beforeAll/afterAll when possible
Keep tests fast, deterministic, and parallelizable
Mock at component boundaries (network, time, context)

Files:

  • packages/arkenv/src/coercion.integration.test.ts
  • packages/arkenv/src/create-env.test.ts
**/*.integration.test.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.integration.test.{ts,tsx}: Use integration tests with *.integration.test.ts or *.integration.test.tsx suffix to test how multiple units work together without mocking their interactions
Integration tests should verify interactions between multiple units (like components + hooks + services) without mocking their interactions

**/*.integration.test.{ts,tsx}: Test how multiple units (components, hooks, functions) work together without mocking their interactions in integration tests
Integration tests should focus on component and hook interactions, function composition and data flow, real dependencies between units, and state synchronization across boundaries
Integration tests should execute between 100ms - 2000ms per test
Use *.integration.test.ts suffix to distinguish integration tests from unit tests

Files:

  • packages/arkenv/src/coercion.integration.test.ts
packages/arkenv/src/**/*.ts

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Main library implementation should be in src/create-env.ts, built-in validators in src/types.ts, error handling in src/errors.ts, and utilities in src/utils.ts

Files:

  • packages/arkenv/src/coercion.integration.test.ts
  • packages/arkenv/src/create-env.test.ts
  • packages/arkenv/src/create-env.ts
**/*.{ts,tsx,json,md}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Biome for linting and formatting instead of ESLint and Prettier

Files:

  • packages/arkenv/src/coercion.integration.test.ts
  • openspec/changes/add-coercion/proposal.md
  • openspec/changes/add-coercion/design.md
  • openspec/changes/add-coercion/tasks.md
  • packages/arkenv/src/create-env.test.ts
  • openspec/changes/add-coercion/specs/coercion/spec.md
  • packages/arkenv/src/create-env.ts
  • packages/internal/keywords/src/index.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
openspec/changes/*/proposal.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Create proposal.md with sections: Why, What Changes (with BREAKING markers), and Impact

Files:

  • openspec/changes/add-coercion/proposal.md
openspec/changes/*/tasks.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

Create tasks.md with numbered implementation checklist items as checkboxes (- [ ] format)

Files:

  • openspec/changes/add-coercion/tasks.md
openspec/changes/*/specs/**/spec.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/changes/*/specs/**/spec.md: Use ## ADDED|MODIFIED|REMOVED|RENAMED Requirements as delta headers in spec.md files under changes
Ensure change proposals contain delta specs for each affected capability under changes/[change-id]/specs/[capability]/spec.md

Files:

  • openspec/changes/add-coercion/specs/coercion/spec.md
openspec/**/spec.md

📄 CodeRabbit inference engine (openspec/AGENTS.md)

openspec/**/spec.md: Include at least one scenario per requirement using #### Scenario: header format with WHEN and THEN bullet points
Use SHALL/MUST for normative requirements rather than should/may unless intentionally non-normative

Files:

  • openspec/changes/add-coercion/specs/coercion/spec.md
**/index.ts

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

Use barrel exports (index.ts) for package entry points

Files:

  • packages/internal/keywords/src/index.ts
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
**/README.md

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Document environment requirements in README files

Files:

  • packages/arkenv/README.md
{bin,examples,playgrounds}/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

Console usage is allowed in bin/ and example/playground directories, otherwise treated as warning

Files:

  • examples/basic/index.ts
examples/**/*.{ts,tsx}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

examples/**/*.{ts,tsx}: Ensure examples demonstrate secure default practices
Add examples in the examples/ directory for new functionality, demonstrating real-world usage patterns

Files:

  • examples/basic/index.ts
🧠 Learnings (33)
📓 Common learnings
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's built-in types (e.g., `string.host`, `number.port`) where possible in environment schemas
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Convert ArkType validation errors to `ArkEnvError` for user-friendly error messages that include variable name and expected type
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Keep environment variable schemas readable and TypeScript-like using ArkType syntax
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use union types for enums in ArkType schemas (e.g., `"'dev' | 'prod'"`) instead of separate enum definitions
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's type inference for TypeScript types instead of manual type definitions
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use ArkType's `type()` function to define schemas in environment variable definitions
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: ArkType validates environment variables at runtime and TypeScript types are inferred from the schema definition
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/coding-guidelines.mdc:0-0
Timestamp: 2025-11-24T16:04:00.957Z
Learning: Applies to **/*.{ts,tsx} : Use `ArkEnvError` for environment variable validation errors
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Use `ArkEnvError` for environment variable errors, not generic errors
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Environment schema definitions should use built-in validators, ArkType string literals, and support default values in the schema pattern
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's built-in types (e.g., `string.host`, `number.port`) where possible in environment schemas

Applied to files:

  • .changeset/cyan-loops-hear.md
  • packages/arkenv/src/coercion.integration.test.ts
  • openspec/changes/add-coercion/proposal.md
  • openspec/changes/add-coercion/design.md
  • .changeset/plain-webs-dig.md
  • openspec/changes/add-coercion/tasks.md
  • packages/arkenv/src/create-env.test.ts
  • openspec/changes/add-coercion/specs/coercion/spec.md
  • packages/arkenv/src/create-env.ts
  • packages/internal/keywords/src/index.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Keep environment variable schemas readable and TypeScript-like using ArkType syntax

Applied to files:

  • .changeset/cyan-loops-hear.md
  • packages/arkenv/src/coercion.integration.test.ts
  • openspec/changes/add-coercion/proposal.md
  • openspec/changes/add-coercion/design.md
  • packages/arkenv/src/create-env.test.ts
  • openspec/changes/add-coercion/specs/coercion/spec.md
  • packages/arkenv/src/create-env.ts
  • packages/internal/keywords/src/index.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use ArkType's `type()` function to define schemas in environment variable definitions

Applied to files:

  • .changeset/cyan-loops-hear.md
  • openspec/changes/add-coercion/proposal.md
  • openspec/changes/add-coercion/design.md
  • packages/arkenv/src/create-env.test.ts
  • openspec/changes/add-coercion/specs/coercion/spec.md
  • packages/arkenv/src/create-env.ts
  • packages/internal/keywords/src/index.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Convert ArkType validation errors to `ArkEnvError` for user-friendly error messages that include variable name and expected type

Applied to files:

  • .changeset/cyan-loops-hear.md
  • packages/arkenv/src/coercion.integration.test.ts
  • openspec/changes/add-coercion/proposal.md
  • packages/arkenv/src/create-env.test.ts
  • packages/arkenv/src/create-env.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use union types for enums in ArkType schemas (e.g., `"'dev' | 'prod'"`) instead of separate enum definitions

Applied to files:

  • .changeset/cyan-loops-hear.md
  • packages/arkenv/src/coercion.integration.test.ts
  • openspec/changes/add-coercion/proposal.md
  • openspec/changes/add-coercion/design.md
  • .changeset/plain-webs-dig.md
  • packages/arkenv/src/create-env.test.ts
  • packages/arkenv/src/create-env.ts
  • packages/internal/keywords/src/index.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Use `ArkEnvError` for environment variable errors, not generic errors

Applied to files:

  • .changeset/cyan-loops-hear.md
  • openspec/changes/add-coercion/proposal.md
  • packages/arkenv/src/create-env.test.ts
  • packages/arkenv/src/create-env.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:04:00.957Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/coding-guidelines.mdc:0-0
Timestamp: 2025-11-24T16:04:00.957Z
Learning: Applies to **/*.{ts,tsx} : Use `ArkEnvError` for environment variable validation errors

Applied to files:

  • .changeset/cyan-loops-hear.md
  • openspec/changes/add-coercion/proposal.md
  • packages/arkenv/src/create-env.test.ts
  • packages/arkenv/src/create-env.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's type inference for TypeScript types instead of manual type definitions

Applied to files:

  • .changeset/cyan-loops-hear.md
  • openspec/changes/add-coercion/proposal.md
  • openspec/changes/add-coercion/design.md
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Environment schema definitions should use built-in validators, ArkType string literals, and support default values in the schema pattern

Applied to files:

  • .changeset/cyan-loops-hear.md
  • packages/arkenv/src/coercion.integration.test.ts
  • openspec/changes/add-coercion/proposal.md
  • openspec/changes/add-coercion/design.md
  • packages/arkenv/src/create-env.test.ts
  • openspec/changes/add-coercion/specs/coercion/spec.md
  • packages/arkenv/src/create-env.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:04:00.957Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/coding-guidelines.mdc:0-0
Timestamp: 2025-11-24T16:04:00.957Z
Learning: Applies to **/*.{ts,tsx} : Use PascalCase for type names (e.g., `ArkEnvError`)

Applied to files:

  • .changeset/cyan-loops-hear.md
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.test.{ts,tsx} : Test edge cases including invalid and missing environment variable values

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
  • packages/arkenv/src/create-env.test.ts
📚 Learning: 2025-12-12T13:20:01.954Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/test-patterns.mdc:0-0
Timestamp: 2025-12-12T13:20:01.954Z
Learning: Applies to **/*.integration.test.{ts,tsx} : Integration tests should focus on component and hook interactions, function composition and data flow, real dependencies between units, and state synchronization across boundaries

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
📚 Learning: 2025-12-12T13:20:01.954Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/test-patterns.mdc:0-0
Timestamp: 2025-12-12T13:20:01.954Z
Learning: Applies to **/*.integration.test.{ts,tsx} : Test how multiple units (components, hooks, functions) work together without mocking their interactions in integration tests

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.integration.test.{ts,tsx} : Use integration tests with `*.integration.test.ts` or `*.integration.test.tsx` suffix to test how multiple units work together without mocking their interactions

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
📚 Learning: 2025-12-12T13:20:01.954Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/test-patterns.mdc:0-0
Timestamp: 2025-12-12T13:20:01.954Z
Learning: Applies to **/*.integration.test.{ts,tsx} : Use *.integration.test.ts suffix to distinguish integration tests from unit tests

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.integration.test.{ts,tsx} : Integration tests should verify interactions between multiple units (like components + hooks + services) without mocking their interactions

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest's `describe`/`it` structure in test files

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
📚 Learning: 2025-12-12T13:20:01.954Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/test-patterns.mdc:0-0
Timestamp: 2025-12-12T13:20:01.954Z
Learning: Applies to **/*.test.{ts,tsx} : Use Vitest for unit and integration tests

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
📚 Learning: 2025-12-12T13:20:01.954Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/test-patterns.mdc:0-0
Timestamp: 2025-12-12T13:20:01.954Z
Learning: Applies to packages/vite-plugin/src/**/*.test.ts : Test the Vite plugin using the with-vite-react example as a fixture and validate that the plugin works with real Vite projects

Applied to files:

  • packages/arkenv/src/coercion.integration.test.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: ArkType validates environment variables at runtime and TypeScript types are inferred from the schema definition

Applied to files:

  • openspec/changes/add-coercion/proposal.md
  • packages/arkenv/src/create-env.ts
  • packages/arkenv/README.md
  • apps/playgrounds/node/index.ts
  • packages/internal/scope/src/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/scope.ts : Define custom types in `scope.ts` using ArkType's scoped type system for reusability across schemas

Applied to files:

  • openspec/changes/add-coercion/design.md
  • .changeset/plain-webs-dig.md
  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use the scoped `$` type system for custom types defined in `scope.ts`

Applied to files:

  • openspec/changes/add-coercion/design.md
  • .changeset/plain-webs-dig.md
  • openspec/changes/add-coercion/tasks.md
  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:05:35.714Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: openspec/AGENTS.md:0-0
Timestamp: 2025-11-24T16:05:35.714Z
Learning: Applies to openspec/changes/*/tasks.md : Create tasks.md with numbered implementation checklist items as checkboxes (- [ ] format)

Applied to files:

  • openspec/changes/add-coercion/tasks.md
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to packages/arkenv/src/**/*.ts : Main library implementation should be in `src/create-env.ts`, built-in validators in `src/types.ts`, error handling in `src/errors.ts`, and utilities in `src/utils.ts`

Applied to files:

  • packages/arkenv/src/create-env.test.ts
  • apps/playgrounds/node/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-24T16:04:00.957Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/coding-guidelines.mdc:0-0
Timestamp: 2025-11-24T16:04:00.957Z
Learning: Applies to **/*.{ts,tsx} : Provide clear, actionable error messages that include the variable name and expected type

Applied to files:

  • packages/arkenv/src/create-env.test.ts
📚 Learning: 2025-11-24T16:04:00.957Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/coding-guidelines.mdc:0-0
Timestamp: 2025-11-24T16:04:00.957Z
Learning: Applies to **/*.{ts,tsx} : Prefer `Number.parseInt` over global `parseInt` (`useNumberNamespace` error)

Applied to files:

  • packages/arkenv/src/create-env.test.ts
  • packages/internal/keywords/src/index.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Use `createEnv(schema)` as the main function for validated environment objects, available as the default export

Applied to files:

  • packages/arkenv/src/create-env.ts
  • apps/playgrounds/node/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Use built-in validators (host, port, url, email) from `src/types.ts` when available instead of custom validation

Applied to files:

  • packages/internal/keywords/src/index.ts
📚 Learning: 2025-09-10T19:35:18.179Z
Learnt from: yamcodes
Repo: yamcodes/arkenv PR: 136
File: .changeset/vast-bananas-win.md:2-3
Timestamp: 2025-09-10T19:35:18.179Z
Learning: The arkenv package is currently in v0.x.x (pre-1.0) development phase, where breaking changes are acceptable in minor version bumps according to semantic versioning conventions.

Applied to files:

  • packages/arkenv/README.md
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Never modify `.env` files in examples or apps - these may contain sensitive configuration

Applied to files:

  • apps/playgrounds/node/.env.example
  • examples/basic/.env.example
📚 Learning: 2025-11-24T16:04:00.957Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/coding-guidelines.mdc:0-0
Timestamp: 2025-11-24T16:04:00.957Z
Learning: Applies to **/*.{ts,tsx} : Use UPPER_SNAKE_CASE for environment variables and constants

Applied to files:

  • apps/playgrounds/node/index.ts
  • examples/basic/index.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Use descriptive environment variable names that indicate purpose and format

Applied to files:

  • examples/basic/.env.example
  • examples/basic/index.ts
🧬 Code graph analysis (6)
packages/arkenv/src/coercion.integration.test.ts (2)
packages/arkenv/src/create-env.ts (1)
  • createEnv (35-52)
packages/arkenv/src/type.ts (1)
  • type (3-3)
packages/arkenv/src/create-env.ts (2)
packages/internal/scope/src/index.ts (2)
  • $ (71-81)
  • $ (83-83)
packages/arkenv/src/index.ts (1)
  • EnvSchema (1-1)
packages/internal/keywords/src/index.ts (1)
packages/arkenv/src/type.ts (1)
  • type (3-3)
apps/playgrounds/node/index.ts (1)
packages/vite-plugin/src/index.ts (1)
  • arkenv (49-86)
packages/internal/scope/src/index.ts (1)
packages/arkenv/src/type.ts (1)
  • type (3-3)
examples/basic/index.ts (3)
apps/playgrounds/js/index.js (1)
  • env (3-7)
examples/basic-js/index.js (1)
  • env (3-7)
packages/vite-plugin/src/index.ts (1)
  • arkenv (49-86)
🪛 dotenv-linter (4.0.0)
apps/playgrounds/node/.env.example

[warning] 2-2: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

examples/basic/.env.example

[warning] 2-2: [EndingBlankLine] No blank line at the end of the file

(EndingBlankLine)

🪛 markdownlint-cli2 (0.18.1)
.changeset/cyan-loops-hear.md

13-13: Hard tabs
Column: 1

(MD010, no-hard-tabs)

openspec/changes/add-coercion/proposal.md

26-26: Multiple headings with the same content

(MD024, no-duplicate-heading)

🔇 Additional comments (13)
.changeset/humble-lizards-judge.md (1)

1-7: Changeset correctly documents the breaking change with a clear migration path.

The YAML front matter is properly formatted, the breaking change is clearly marked, and users are guided toward both the standard ArkType boolean keyword and the new arkenv coercion feature. Approved.

apps/playgrounds/node/.env.example (1)

1-2: Clarify the removal of NODE_ENV, DEBUG, and ZED_ENV variables.

The changes remove three environment variables without documented justification. Based on learnings in the repository, .env files in examples should be carefully managed, especially when removing previously required configuration.

Please verify:

  1. Whether the playground still functions correctly without NODE_ENV, DEBUG, and ZED_ENV.
  2. Whether this simplification is intentional for the coercion example, or if these variables are still required.
  3. Update documentation or comments if this removal is intentional to avoid confusion for developers using the example.
packages/arkenv/src/create-env.test.ts (1)

65-65: LGTM! Improved error message specificity.

The updated error message now shows the exact string value with quotes, making it clearer what value failed validation. This improves the developer experience when debugging type mismatches.

.changeset/plain-webs-dig.md (1)

1-9: LGTM! Clear and concise changeset.

The changeset properly documents the scope-based coercion feature with appropriate detail for a patch-level change.

openspec/changes/add-coercion/tasks.md (1)

1-8: LGTM! Well-structured task checklist.

The task list follows the required format and comprehensively covers both implementation and verification steps for the coercion feature.

Based on learnings, the format adheres to the guideline for openspec/changes/*/tasks.md files.

packages/arkenv/README.md (1)

62-76: LGTM! Excellent documentation addition.

The Coercion section clearly explains the automatic type conversion feature with practical examples. The placement and formatting are appropriate for user-facing documentation.

openspec/changes/add-coercion/specs/coercion/spec.md (1)

1-42: LGTM! Well-structured specification.

The specification follows the required format with proper delta headers (## ADDED Requirements), uses MUST for normative requirements, and includes clear Given-When-Then scenarios for each requirement.

As per coding guidelines, the spec properly uses delta headers for change proposals.

packages/arkenv/src/create-env.ts (1)

41-44: LGTM! Good readability improvement.

Extracting the compiled type check into a named boolean variable improves code clarity without changing behavior. The added comment also helps document the validation step.

openspec/changes/add-coercion/proposal.md (1)

1-29: Good proposal structure and content.

The proposal clearly articulates the problem, shows practical before/after examples, and explains the scope-based solution approach. The content effectively communicates the value of the coercion feature.

.changeset/cyan-loops-hear.md (1)

1-31: Good changeset documentation.

The changeset clearly documents the Native Coercion Support feature with practical examples showing usage and expected results. Well-written for end users.

packages/arkenv/src/coercion.integration.test.ts (1)

1-96: Excellent integration test coverage!

The test suite comprehensively validates the coercion feature across multiple scenarios:

  • Basic number and boolean coercion
  • Number subtypes (port, integer, epoch)
  • Error cases (invalid strings, out-of-range values)
  • Mixed coerced and non-coerced schemas
  • Compiled type definitions
  • Natural type inputs without coercion

The tests appropriately verify interactions between createEnv, type, and the scope's coercion logic without mocking, which aligns with integration test best practices.

openspec/changes/add-coercion/design.md (1)

1-46: Clear design documentation.

The design document provides a well-structured explanation of the scope-based coercion approach, including architecture flow, decision rationale, and implementation guidance. The documented approach aligns with the actual implementation in packages/internal/scope/src/index.ts and packages/internal/keywords/src/index.ts.

packages/internal/scope/src/index.ts (1)

11-22: The NaN handling and narrow check are correct.

When Number(s) produces NaN (line 15), the string is returned and passed to the narrow() check, which rejects it with "must be a number". This prevents any non-numeric strings from reaching number sub-keywords (like number.port, number.integer, number.epoch) because the wrapModule function pipes coercedNumber into each sub-keyword, ensuring the narrow check always validates output before sub-keyword processing. The implementation is safe and properly guards sub-keywords from receiving invalid string values.

…oolean coercion, and add known limitations with workarounds.
@yamcodes
Copy link
Owner Author

@coderabbitai review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 20, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
packages/internal/scope/src/index.ts (2)

11-22: Consider clarifying the NaN handling logic.

The ternary on line 15 returns the original string s when Number.isNaN(n) is true, which then gets rejected by the .narrow() validator. While this follows ArkType's pattern of letting validation happen in .narrow(), the logic could be more explicit about the failure path.

Alternative: Add a comment to clarify intent
 const coercedNumber = type("string | number")
 	.pipe((s) => {
 		if (typeof s === "number") return s;
 		const n = Number(s);
+		// Return original string if conversion fails; narrow() will reject it
 		return Number.isNaN(n) ? s : n;
 	})
 	.narrow((data, ctx): data is number => {
 		if (typeof data !== "number") {
 			return ctx.mustBe("a number");
 		}
 		return true;
 	});

24-34: LGTM with a minor naming suggestion.

The coercion logic correctly handles both string and boolean inputs. The parameter name str might be slightly misleading since the input can be a boolean, but the implementation is correct.

Optional: Rename parameter for clarity
 const coercedBoolean = type(
 	"'true' | 'false' | true | false",
 	"=>",
-	(str) => str === "true" || str === true,
+	(value) => value === "true" || value === true,
 );
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b984be and 8ebd095.

📒 Files selected for processing (5)
  • .changeset/cyan-loops-hear.md (1 hunks)
  • apps/playgrounds/node/.env.example (0 hunks)
  • examples/basic/.env.example (0 hunks)
  • openspec/changes/add-coercion/proposal.md (1 hunks)
  • packages/internal/scope/src/index.ts (2 hunks)
💤 Files with no reviewable changes (2)
  • examples/basic/.env.example
  • apps/playgrounds/node/.env.example
🚧 Files skipped from review as they are similar to previous changes (2)
  • openspec/changes/add-coercion/proposal.md
  • .changeset/cyan-loops-hear.md
🧰 Additional context used
📓 Path-based instructions (3)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

**/*.{ts,tsx}: Prefer type over interface for type definitions in TypeScript
Use TypeScript 5.1+ features when appropriate
Leverage const type parameters for better inference in TypeScript
Use JSDoc comments for public APIs
Use tabs for indentation (configured in Biome)
Use double quotes for strings (configured in Biome)
Organize imports automatically (Biome handles this)
Avoid explicit types when TypeScript can infer them (noInferrableTypes error)
Use as const where appropriate for immutable values (useAsConstAssertion error)
Don't reassign function parameters (noParameterAssign error)
Place default parameters last in function signatures (useDefaultParameterLast error)
Always initialize enum values (useEnumInitializers error)
Declare one variable per statement (useSingleVarDeclarator error)
Avoid unnecessary template literals (noUnusedTemplateLiteral error)
Prefer Number.parseInt over global parseInt (useNumberNamespace error)
Use kebab-case for TypeScript filenames (e.g., create-env.ts)
Use camelCase for function names (e.g., createEnv)
Use PascalCase for type names (e.g., ArkEnvError)
Use UPPER_SNAKE_CASE for environment variables and constants
Include examples in JSDoc comments when helpful for public APIs
Document complex type logic with JSDoc comments
Use ArkEnvError for environment variable validation errors
Provide clear, actionable error messages that include the variable name and expected type

**/*.{ts,tsx}: Use createEnv(schema) as the main function for validated environment objects, available as the default export
Use built-in validators (host, port, url, email) from src/types.ts when available instead of custom validation
Use ArkEnvError for environment variable errors, not generic errors
Environment schema definitions should use built-in validators, ArkType string literals, and support default values in the schema pattern
Use logical grouping for related environment variables in schemas
Use descriptive env...

Files:

  • packages/internal/scope/src/index.ts
**/index.ts

📄 CodeRabbit inference engine (.cursor/rules/coding-guidelines.mdc)

Use barrel exports (index.ts) for package entry points

Files:

  • packages/internal/scope/src/index.ts
**/*.{ts,tsx,json,md}

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Use Biome for linting and formatting instead of ESLint and Prettier

Files:

  • packages/internal/scope/src/index.ts
🧠 Learnings (12)
📓 Common learnings
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's built-in types (e.g., `string.host`, `number.port`) where possible in environment schemas
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Keep environment variable schemas readable and TypeScript-like using ArkType syntax
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/scope.ts : Define custom types in `scope.ts` using ArkType's scoped type system for reusability across schemas
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Convert ArkType validation errors to `ArkEnvError` for user-friendly error messages that include variable name and expected type
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Environment schema definitions should use built-in validators, ArkType string literals, and support default values in the schema pattern
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use union types for enums in ArkType schemas (e.g., `"'dev' | 'prod'"`) instead of separate enum definitions
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use the scoped `$` type system for custom types defined in `scope.ts`
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use the scoped `$` type system for custom types defined in `scope.ts`

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's type inference for TypeScript types instead of manual type definitions

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:04:00.957Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/coding-guidelines.mdc:0-0
Timestamp: 2025-11-24T16:04:00.957Z
Learning: Applies to **/*.{ts,tsx} : Use `as const` where appropriate for immutable values (`useAsConstAssertion` error)

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Keep environment variable schemas readable and TypeScript-like using ArkType syntax

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Leverage ArkType's built-in types (e.g., `string.host`, `number.port`) where possible in environment schemas

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use union types for enums in ArkType schemas (e.g., `"'dev' | 'prod'"`) instead of separate enum definitions

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/scope.ts : Define custom types in `scope.ts` using ArkType's scoped type system for reusability across schemas

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Use ArkType's `type()` function to define schemas in environment variable definitions

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: Applies to packages/arkenv/**/*.ts : Convert ArkType validation errors to `ArkEnvError` for user-friendly error messages that include variable name and expected type

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-29T22:11:39.931Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-11-29T22:11:39.931Z
Learning: Applies to **/*.{ts,tsx} : Use `ArkEnvError` for environment variable errors, not generic errors

Applied to files:

  • packages/internal/scope/src/index.ts
📚 Learning: 2025-11-24T16:03:45.295Z
Learnt from: CR
Repo: yamcodes/arkenv PR: 0
File: .cursor/rules/arktype.mdc:0-0
Timestamp: 2025-11-24T16:03:45.295Z
Learning: ArkType validates environment variables at runtime and TypeScript types are inferred from the schema definition

Applied to files:

  • packages/internal/scope/src/index.ts
🧬 Code graph analysis (1)
packages/internal/scope/src/index.ts (1)
packages/arkenv/src/type.ts (1)
  • type (3-3)
🔇 Additional comments (3)
packages/internal/scope/src/index.ts (3)

1-2: LGTM!

The imports correctly bring in the necessary dependencies for the coercion functionality.


75-85: LGTM!

The scope integration correctly applies coercion to the number module (including all its sub-keywords like integer and port) and replaces the boolean keyword with the coerced variant. This aligns with the PR objectives for scope-level coercion of strings to numbers and booleans.


36-70: The runtime arkKind check provides sufficient type safety for the pipe operation.

The as any casting on line 62 is justified by the complexity of ArkType's internal Module typing. The pipe() method is a standard feature of ArkType's Type interface, and the runtime check arkKind === "type" (lines 54–56) verifies that the value is an ArkType instance. All ArkTypes have the pipe method available by design, so the runtime check provides sufficient guarantees. The explanatory comment adequately documents why the cast is necessary, making this a pragmatic solution to the typing limitations without introducing runtime safety risks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arkenv Changes to the `arkenv` npm package. docs Improvements or additions to documentation example Issues or Pull Requests concerning at least one ArkEnv example. (Found in the `examples/` directory) tests This issue or PR is about adding, removing or changing tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implementation

1 participant