You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Architectural Strategy: Internal vs. Published Packages
Based on the recent decisions around @arkenv/standard, the build-tools ADR, and the context from PR #1162 (consolidating @repo/scope to avoid version skew), here is my recommendation.
The short answer is: You need a hybrid approach, but the deciding factor is whether the shared code is Build-Time or Runtime.
You shouldn't "double down" universally on either approach. Instead, apply the following matrix:
Recommendation: Migrate to Published NPM Packages (e.g., @arkenv/build-utils).
Tip
This perfectly aligns with the build-tools ADR (Sub-alternative B). Publish the package, but explicitly mark it as "internal use only" in the README.
Why this is the right call:
The Edge Constraint: Build tools require Node globals (fs, path) and heavy dependencies (chokidar, TypeScript AST). You absolutely cannot inline or export these from @arkenv/core without instantly breaking Edge compatibility (Vercel, Cloudflare) and destroying the "zero dependencies" promise.
The Monorepo Constraint: If you keep them as internal @repo/* packages and don't inline them, consuming projects (like Next.js apps on Vercel) will crash during deployment because they can't resolve the unpublished turborepo package.
The Bloat Constraint: If you do inline them into every framework plugin (@arkenv/nextjs, @arkenv/vite-plugin), you drastically bloat the size of every plugin.
Publishing @arkenv/build-utils as a public dependency cleanly solves all three. The cost of changesets/registry clutter is entirely worth the stability it brings to the deployment pipeline.
2. Shared Runtime Logic (types, keywords, scopes)
Recommendation: Double down on Internal Turborepo Packages (@repo/*) and bundle them, OR use Singleton Subpath Exports (like PR #1162). Do not publish these as separate public NPM packages.
Why this is the right call:
The Singleton Problem: As you discovered in PR (v1) Internal code sharing strategy #1162, ArkType scopes are stateful. If you publish @arkenv/scope or @arkenv/types as standalone packages, users will inevitably end up with mismatched versions (e.g., @arkenv/core uses v1.0.1 but @arkenv/standard uses v1.0.2). This leads to duplicate instances in node_modules and catastrophic "version skew" bugs where type guarding fails.
Registry Clutter: Publishing hyper-granular packages like @arkenv/types creates an explosion of packages to manage and version.
The Runtime Strategy:
For runtime code, you have two safe patterns depending on the statefulness of the code:
For Stateful Logic (e.g., ArkType Scopes):
Use the Singleton Subpath Export pattern you introduced in PR (v1) Internal code sharing strategy #1162.
Host the canonical instance in @arkenv/core and expose it internally via "exports": { "./internal": "./dist/internal.js" }. Have your plugins import from @arkenv/core/internal. This guarantees exactly one instance of the scope is ever loaded in memory.
For Pure Logic (e.g., utility functions, type definitions):
Use the Inlined Internal turborepo Package pattern.
Keep them in packages/internal/@repo/utils, and use tsup/tsdown to bundle/inline them directly into the dist output of @arkenv/core and @arkenv/standard. It remains completely invisible to the end user and NPM.
Summary Verdict
Shared Domain
Code Type
Strategy
Reason
Build Tools
Codegen, Watchers, AST
Published NPM Package (@arkenv/build-utils)
Prevents deployment crashes and plugin bloat while preserving Edge compatibility.
Stateful Runtime
ArkType Scopes, Caches
Subpath Export (@arkenv/core/internal)
Prevents duplicate instances in memory and version skew.
Pure Runtime
Type utilities, helpers
Inlined Turborepo Package (@repo/utils)
Keeps NPM clean; zero impact on end users.
Given the direction we set with @arkenv/standard, this hybrid model gives you the perfect balance: a rock-solid, zero-dependency runtime, and a decoupled, robust build-time toolchain.
Architectural Strategy: Internal vs. Published Packages
Based on the recent decisions around
@arkenv/standard, thebuild-toolsADR, and the context from PR #1162 (consolidating@repo/scopeto avoid version skew), here is my recommendation.The short answer is: You need a hybrid approach, but the deciding factor is whether the shared code is Build-Time or Runtime.
You shouldn't "double down" universally on either approach. Instead, apply the following matrix:
1. Build-Time Infrastructure (
build-tools, codegen, AST parsers)Recommendation: Migrate to Published NPM Packages (e.g.,
@arkenv/build-utils).Tip
This perfectly aligns with the
build-toolsADR (Sub-alternative B). Publish the package, but explicitly mark it as "internal use only" in the README.Why this is the right call:
fs,path) and heavy dependencies (chokidar, TypeScript AST). You absolutely cannot inline or export these from@arkenv/corewithout instantly breaking Edge compatibility (Vercel, Cloudflare) and destroying the "zero dependencies" promise.@repo/*packages and don't inline them, consuming projects (like Next.js apps on Vercel) will crash during deployment because they can't resolve the unpublished turborepo package.@arkenv/nextjs,@arkenv/vite-plugin), you drastically bloat the size of every plugin.Publishing
@arkenv/build-utilsas a public dependency cleanly solves all three. The cost of changesets/registry clutter is entirely worth the stability it brings to the deployment pipeline.2. Shared Runtime Logic (
types,keywords,scopes)Recommendation: Double down on Internal Turborepo Packages (
@repo/*) and bundle them, OR use Singleton Subpath Exports (like PR #1162). Do not publish these as separate public NPM packages.Why this is the right call:
@arkenv/scopeor@arkenv/typesas standalone packages, users will inevitably end up with mismatched versions (e.g.,@arkenv/coreusesv1.0.1but@arkenv/standardusesv1.0.2). This leads to duplicate instances innode_modulesand catastrophic "version skew" bugs where type guarding fails.@arkenv/typescreates an explosion of packages to manage and version.The Runtime Strategy:
For runtime code, you have two safe patterns depending on the statefulness of the code:
For Stateful Logic (e.g., ArkType Scopes):
Use the Singleton Subpath Export pattern you introduced in PR (v1) Internal code sharing strategy #1162.
Host the canonical instance in
@arkenv/coreand expose it internally via"exports": { "./internal": "./dist/internal.js" }. Have your plugins import from@arkenv/core/internal. This guarantees exactly one instance of the scope is ever loaded in memory.For Pure Logic (e.g., utility functions, type definitions):
Use the Inlined Internal turborepo Package pattern.
Keep them in
packages/internal/@repo/utils, and usetsup/tsdownto bundle/inline them directly into the dist output of@arkenv/coreand@arkenv/standard. It remains completely invisible to the end user and NPM.Summary Verdict
@arkenv/build-utils)@arkenv/core/internal)@repo/utils)Given the direction we set with
@arkenv/standard, this hybrid model gives you the perfect balance: a rock-solid, zero-dependency runtime, and a decoupled, robust build-time toolchain.