Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
552a9a8
feat: add turborepo skill and ADRs 0007 & 0008 from analyze-zod-expor…
yamcodes Jun 12, 2026
8defbe3
docs: remove ADR 8 (belongs to issue 1200)
yamcodes Jun 13, 2026
1c6fc7f
docs: add ADR 0010 for runtime shared logic strategy
yamcodes Jun 13, 2026
8e35d3c
docs: standardize product name as ArkEnv in ADR and CLI changelog
yamcodes Jun 16, 2026
6d55945
docs: add ADR 0008 to document the migration from createEnv to arkenv…
yamcodes Jun 16, 2026
cc0b227
Please provide the list of changes or the files modified so I can gen…
yamcodes Jun 16, 2026
45d9f4f
Merge branch 'v1' into 1199-distribution-implement-standard-mode-pack…
yamcodes Jun 16, 2026
ba951a2
Merge branch 'v1' into 1199-distribution-implement-standard-mode-pack…
yamcodes Jun 19, 2026
bca2fe0
feat: implement hybrid versioning by decoupling plugins and enforcing…
yamcodes Jun 19, 2026
9f20282
feat: move arkenv to peer dependencies and update changeset documenta…
yamcodes Jun 19, 2026
26663c3
docs: add README for @arkenv/build and clarify runtime strategy in AD…
yamcodes Jun 19, 2026
036127b
docs: clarify independent versioning and fix package name references
yamcodes Jun 20, 2026
185c89a
docs: add rule for future core/standard split in versioning strategy
yamcodes Jun 20, 2026
641b4e2
docs(adr): document peer dependency rationale and CLI onboarding flow
yamcodes Jun 20, 2026
f8ef94b
docs(adr): resolve review feedback on independent versioning examples…
yamcodes Jun 20, 2026
d88cbe8
refactor: decouple plugins from core engine by replacing workspace pe…
yamcodes Jun 20, 2026
0f5e61a
docs: add enterprise precedent for peer dependency strategy in ADR 0011
yamcodes Jun 20, 2026
61340e1
Merge remote-tracking branch 'origin/dev' into 1199-distribution-impl…
yamcodes Jun 23, 2026
a970f14
Merge branch 'v1' into 1199-distribution-implement-standard-mode-pack…
yamcodes Jun 23, 2026
7820207
docs: resolve changeset peerDependency range review comment and AutoT…
yamcodes Jun 23, 2026
a710669
Merge branch 'v1' into 1199-distribution-implement-standard-mode-pack…
yamcodes Jun 23, 2026
57b30a5
refactor: rename nuxt internal file to arkenv-internal
yamcodes Jun 23, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions docs/adr/0007-standard-mode-packaging-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Standard Mode Packaging Strategy

## Context

With the introduction of the "Standard Schema" implementation (which strictly removes ArkType dependencies) and the migration to the `@arkenv` scope, we face a decision on how to distribute the "Standard Mode" across the core library and its framework plugins.

The decision is asymmetric:

1. **The Core Engine:** We are splitting the core into two separate packages (`@arkenv/core` for ArkType, and `@arkenv/standard` for Standard Schema).
2. **The Framework Plugins:** We are **not** splitting the plugins into separate packages (e.g., we will not create `@arkenv/vite-plugin-standard`). Instead, a single plugin package will support both engines via subpath exports (e.g., `import arkenv from "@arkenv/vite-plugin/standard"`).

## Decision: Why the Asymmetry?

We have chosen this asymmetric packaging strategy for three primary reasons:

### 1. The N-Multiplier Effect

If we mandate that every engine requires a separate package, adding support for a new framework requires creating two new packages (e.g., `@arkenv/bun-plugin` and `@arkenv/bun-plugin-standard`). If we ever add a third engine, we'd need three packages per framework. By using subpath exports in the plugins, we maintain a 1:1 ratio between frameworks and plugin packages. This dramatically reduces repository and npm namespace bloat.

### 2. Perfect Sibling Symmetry & Discoverability

By extracting `@arkenv/standard` into its own package, we achieve perfect sibling symmetry: `@arkenv/core` represents the primary ArkType engine, and `@arkenv/standard` represents the alternative Standard Schema engine. This elevates Standard Mode to a first-class citizen, providing a massive discoverability benefit for Zod and Valibot users who no longer have to install a package whose primary identity is built around ArkType.

### 3. Zero Peer Dependency Confusion

The engines have fundamentally different dependency requirements. The core requires `arktype`, while the standard mode is dependency-free. Splitting them allows `@arkenv/core` to list `arktype` as a *required* peer dependency, while `@arkenv/standard` has zero peer dependencies. This is much cleaner for package managers than relying on complex `peerDependenciesMeta: { optional: true }` configurations at a unified core level.

### 4. Shared Internals (Enabler)

This split is made architecturally feasible because we are already adopting an "inlined internal packages" strategy. We can easily extract the shared `parse-standard` and `guards` logic into an internal `@repo/utils` package, and bundle that into both `@arkenv/core` and `@arkenv/standard` at publish time without creating dual-package hazards.

### 5. Plugins are Thin Routers

Framework plugins do not implement parsing or evaluation logic; they are essentially dependency-injectors that bridge the build tool to the engine. Because they are lightweight, a single plugin package can safely list both `@arkenv/core` and `@arkenv/standard` as optional peer dependencies. The user installs the engine they want, imports the correct plugin subpath, and the bundler tree-shakes the unused path. There is no risk of leaking heavy dependencies.

## Consequences

- **Positive:** The npm ecosystem remains clean (fewer packages).
- **Positive:** Users only download the dependencies they actually need (ArkType vs Standard).
- **Negative:** Plugin maintainers must ensure their `package.json` correctly exposes multiple `"exports"` and manages dual optional peer dependencies.
59 changes: 59 additions & 0 deletions docs/adr/0009-runtime-shared-logic-strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Runtime Shared Logic Strategy

## Context

ArkEnv relies on several internal utility functions and runtime configurations that must be shared across our core engines (`@arkenv/core` and `@arkenv/standard`) as well as framework plugins (e.g., `@arkenv/nextjs`, `@arkenv/nuxt`). This creates an architectural decision regarding how to distribute and share this logic effectively without compromising developer experience, type safety, or build environments.

When comparing code-sharing strategies, two traditional approaches are often cited:

1. **The NestJS Peer Dependency Model:** Extensive use of `peerDependencies` where heavy singletons are hoisted to the application root.
2. **The Published Utility Package:** Creating a public `@arkenv/utils` package published to npm that all other packages depend on.

Both of these standard approaches introduce critical flaws for ArkEnv's unique requirements as a structural typing utility.

## Decision

We will avoid peer dependencies for code sharing and avoid publishing an external utility package.

Instead, we will adopt an **Inlined Internal Packages Strategy** (`@repo/utils`) combined with **Subpath Exports** for sharing internal logic.

1. **Stateless Logic (Helpers/Parsers):** Shared stateless logic will live in an internal monorepo package (e.g., `@repo/utils`). Using our bundler (`tsdown`), this logic will be physically inlined into the distributables of the consuming published packages (`@arkenv/core`, `@arkenv/standard`).
2. **Stateful Logic (Singletons/Schemas):** Any stateful logic or singleton configuration will be managed via Subpath Exports directly from the core packages, rather than using peer dependencies to enforce a single instance.

## Rationale

### 1. Why Not The NestJS Peer Dependency Model?

NestJS is a heavy, opinionated framework. It extensively uses `peerDependencies` (e.g., `@nestjs/core`, `@nestjs/common`) and relies on the user's package manager (npm/pnpm/yarn) to hoist these dependencies to the root of the project, theoretically ensuring a single instance of the framework's IoC container across the app.

This works for NestJS because users expect a rigid, complex dependency graph within a tightly controlled application boundary.

However, this is fundamentally incompatible with ArkEnv for several reasons:

- **Version Skew and Phantom Dependencies:** Monorepo package managers notoriously struggle with strict peer dependency hoisting. If a user installs a slightly different version of a sub-package, the package manager may duplicate the dependency in `node_modules`.
- **Fatal for Structural Typing:** ArkEnv relies heavily on ArkType, which evaluates schemas using structural typing, `instanceof` checks, and strict object identity under the hood. If a duplicated instance of the core engine or ArkType is loaded due to peer dependency failure, schema evaluation and type scoping break silently at runtime. We cannot afford to trust hoisting for singleton integrity.

### 2. Why Not a Published `@arkenv/utils` Package?

The standard alternative to peer dependencies is a centralized utility package. We could create an `@arkenv/utils` package and publish it to npm, having `@arkenv/core` and `@arkenv/standard` depend on it normally.

This introduces unacceptable friction:

- **Public API Surface Bloat:** We would be forced to publish purely internal implementation details to npm.
- **Dependency Graph Bloat:** Users must download additional packages, slowing down install times and increasing the overall node_modules footprint.
- **Cascading Version Bumps:** Updating a single internal helper function would require bumping `@arkenv/utils`, which then requires bumping `@arkenv/core` and `@arkenv/standard`, and likely all the framework plugins, turning minor internal refactors into massive versioning chores.

### 3. The Power of Inlined Internal Packages

By keeping `@repo/utils` purely as a local workspace package, we achieve the best of both worlds:

- **Zero Distribution Cost:** Using `tsdown`, we inline the specific functions we need directly into the published bundles. At runtime, the user downloads zero external utility packages. The logic is fully self-contained.
- **Excellent Monorepo DX:** We maintain the developer experience of sharing typed code across our monorepo during development.
- **Build Tool Stability:** Inlined code avoids the issue where "build tools crash on Edge runtimes." If we used external packages that inadvertently pulled in Node-native modules (like `fs` or `path`), Edge deployment platforms (like Vercel) would crash. Inlining allows us to strictly control the exact code entering the runtime payload.

## Consequences

- **Positive:** No version skew or runtime duplication risks.
- **Positive:** Faster user installs with fewer packages.
- **Positive:** Reduced version bump cascading.
- **Negative:** The compiled bundle size of the core packages might be trivially larger due to inlined helpers, but this is mitigated by tree-shaking and the lightweight nature of the utilities.
2 changes: 1 addition & 1 deletion packages/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@

- #### Respect `tsconfig.json` for path resolution and scaffolding _[`#1013`](https://github.com/yamcodes/arkenv/pull/1013) [`0a18edd`](https://github.com/yamcodes/arkenv/commit/0a18edd97564b5b178bd20235a1bb0c20ed375ab) [@yamcodes](https://github.com/yamcodes)_

The Arkenv CLI now dynamically resolves configuration paths and scans project files by respecting `tsconfig.json` settings (`rootDir`, `paths`, `baseUrl`).
The ArkEnv CLI now dynamically resolves configuration paths and scans project files by respecting `tsconfig.json` settings (`rootDir`, `paths`, `baseUrl`).

Key improvements include:

Expand Down
Loading