Skip to content

fix(schema, type): avoid ParseError on duplicate scope names and decouple parseCache - #1654

Merged
ssalbdivad merged 1 commit into
arktypeio:mainfrom
yamcodes:fix-attest-peer-dependency-and-duplicate-scope
Sep 9, 2026
Merged

fix(schema, type): avoid ParseError on duplicate scope names and decouple parseCache#1654
ssalbdivad merged 1 commit into
arktypeio:mainfrom
yamcodes:fix-attest-peer-dependency-and-duplicate-scope

Conversation

@yamcodes

@yamcodes yamcodes commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Resolves #1617 and unblocks yamcodes/arkenv#895

Summary

In @ark/schema, BaseScope previously threw A Scope already named ${this.name} already exists if a scope was registered with a name that was already recorded in scopesByName.

Because ArkType's built-in arkArray keyword module is initialized at module evaluation time with { name: "Array" }, any dual-package hazard (e.g. @ark/attest running alongside another version of arktype in a monorepo) or Next.js/Vite HMR module re-evaluation would throw an uncatchable ParseError: A Scope already named Array already exists during startup.

This PR:

  1. Removes the duplicate-name throwParseError in @ark/schema/scope.ts and uses a sequential monotonic counter (anonymousScopeCount) for anonymous scopes (dropping the dead scopesByName state).
  2. Decouples parseCache in @ark/type/parser/definition.ts from scope names by keying on BaseScope identity using a WeakMap<BaseScope, Record<string, InnerParseResult>>. This ensures parse results are isolated per scope instance even if two scopes share the same name, and prevents memory leaks when temporary scopes are garbage-collected.
  3. Adds unit tests in both @ark/schema and arktype asserting that scopes sharing the same name do not throw, and that parseCache does not leak between same-named scope instances.

Why this approach over alternatives?

  • Why not just change @ark/attest/package.json (e.g. peerDependencies or bumping versions)?
    Only changing package.json does not solve the root bug. Any time two slightly different versions of arktype coexist in a monorepo, or when a file is re-evaluated during dev-server HMR (Vite / Next.js), the built-in Array module would still throw a fatal startup crash. Changing peerDependencies also alters the installation contract for existing @ark/attest users and churns lockfiles.
  • Why use a WeakMap for parseCache instead of scope names?
    When multiple scopes share the same name, string definitions parsed in one scope could leak into another. Keying the cache by BaseScope object reference guarantees 100% parse isolation per instance, and allows temporary scopes to be cleanly garbage-collected without memory leaks.

Verification

  • pnpm lint: passed with 0 warnings/errors.
  • pnpm prChecks: passed completely locally (lint, buildRepo, tsc, mocha unit tests, V8 fast properties, integration tests, benchmark thresholds, and tsVersions tests: 1713 passing).

@github-project-automation github-project-automation Bot moved this to To do in arktypeio Sep 3, 2026
@yamcodes
yamcodes force-pushed the fix-attest-peer-dependency-and-duplicate-scope branch from e7a4ae7 to 4adba5c Compare September 3, 2026 06:05
@yamcodes yamcodes changed the title fix(schema, attest): resolve duplicate scope collisions and make arktype a peerDependency in attest fix(schema): avoid ParseError when scopes share the same name Sep 3, 2026

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Important

Relaxing scope-name uniqueness silently breaks @ark/type's name-keyed parse cache. Worth addressing before merge.

Reviewed changes

  • @ark/schema scope registration — removed the duplicate-name throwParseError, replaced the name-derived anonymous counter with a monotonic anonymousScopeCount, and now register/overwrite scopesByName[this.name] unconditionally so arkArray re-evaluation (dual-package hazard / HMR) no longer crashes.
  • @ark/attest packaging — moved arktype from dependencies to peerDependencies (>=2.0.0) with a devDependencies entry for the workspace, and dropped the unused @ark/schema dependency.
  • Tests — added @ark/schema and arktype tests asserting two scopes named "Array" can coexist without throwing.
  • pnpm-lock.yaml — regenerated for the attest dependency reshuffle.

ℹ️ Nitpicks

  • ark/schema/scope.ts:153-154scopesByName is now entirely write-only dead state (the throw it previously guarded was its only reader). Once the parse-cache keying in the main comment is decoupled from scope names, this map and the name property become pure diagnostics and could be removed or documented as such.
  • ark/attest/package.json:55peerDependencies: "arktype": ">=2.0.0" admits future 3.x majors, which will break attest's direct import { type } from "arktype" reliance on arktype internals. ^2.0.0 is more honest about the semver contract (and the author's own test matrix only exercises 2.x).

Pullfrog  | Fix all ➔Fix 👍s ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

Comment thread ark/schema/scope.ts

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ Incremental delta is clean — the prior @ark/schema finding remains open.

Reviewed changes

  • Reverted @ark/attest packaging change — commit 4adba5c6 drops the ark/attest/package.json and pnpm-lock.yaml edits from the prior revision, narrowing this PR to the @ark/schema scope-collision fix only. This resolves the peer-dependency nitpick from the previous review (the "arktype": ">=2.0.0" peer-range concern is no longer part of the diff).

The remaining schema change and its two tests are unchanged from the prior review. The inline finding on ark/schema/scope.ts:278 (the @ark/type parse-cache keyed by scope name, in ark/type/parser/definition.ts:69) is unaffected by this commit and remains open.

Pullfrog  | Fix it ➔View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@yamcodes
yamcodes force-pushed the fix-attest-peer-dependency-and-duplicate-scope branch from 4adba5c to 8a87cd8 Compare September 3, 2026 06:11
@yamcodes yamcodes changed the title fix(schema): avoid ParseError when scopes share the same name fix(schema, type): avoid ParseError on duplicate scope names and decouple parseCache Sep 3, 2026
…uple parseCache by scope identity

- Avoid throwing ParseError when multiple scopes share the same name (e.g. built-in 'Array' scope re-evaluated during HMR, multi-package monorepos, or dual-package instances)
- Key parseCache by BaseScope instance via WeakMap instead of scope name to prevent cache collision across same-named scopes and avoid memory leaks
- Use sequential counter for anonymous scopes and remove dead scopesByName map
- Add unit tests verifying scope name tolerance and parseCache isolation

Closes arktypeio#1617
@yamcodes
yamcodes force-pushed the fix-attest-peer-dependency-and-duplicate-scope branch from 8a87cd8 to d8c628f Compare September 3, 2026 06:27
@yamcodes

yamcodes commented Sep 3, 2026

Copy link
Copy Markdown
Contributor Author

@pullfrog review please

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

✅ No new issues found.

Reviewed changes

  • Duplicate-scope-name tolerance in @ark/schema — removed the write-only scopesByName ledger and its "already named" throwParseError, replacing anonymous-scope naming with a module-local monotonic counter. Verified via repo-wide grep that scopesByName (and scope.name reads outside the constructor) had no other consumers.
  • Per-scope parse cache in @ark/typeparseCache now keys by BaseScope instance through a WeakMap<BaseScope, Record<string, InnerParseResult>>, decoupling parse results between same-named scopes (including the builtin Array keyword scope triggered by @ark/attest dual-package/HMR reloads)and letting temporary scopes be GC'd. ctx.$ is always the owning BaseScope (createParseContext stamps it), so no WeakMap key mismatch ins any parseInnerDefinition call path.
  • Regression tests — new unit tests in both @ark/schema and arktype simulate two scopes named "Array" (the builtin keyword scope name),asserting construction no longer throws; the arktype test additionally pins that type("a") resolves independently per scope. Both new tests fail against the pre-fix code (construction throw; name-keyed cache returning the other scope's "a" node)and pass now. pnpm tsc and the scoped mocha suites clear (the only 2 failing entries arethe known-unrelated ark/attest snapPopulation noise when ark/attest/out isn't built).

The root-cause framing in the issue (#1617) isčorrect: fixing @ark/attest's dependency pin alone wouldn't survive HMR re-evaluation, so making scope construction tolerant of duplicate names and keying parseCache by scope identity is the right depth of fix.

Pullfrog  | View workflow run | Using DeepSeek Flash (free via Pullfrog for OSS) | 𝕏

@ssalbdivad ssalbdivad left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

great catch, thanks!

@ssalbdivad
ssalbdivad merged commit d2d6bac into arktypeio:main Sep 9, 2026
7 checks passed
@github-project-automation github-project-automation Bot moved this from To do to Done (merged or closed) in arktypeio Sep 9, 2026
@yamcodes
yamcodes deleted the fix-attest-peer-dependency-and-duplicate-scope branch September 9, 2026 18:54
@yamcodes

Copy link
Copy Markdown
Contributor Author

Thanks @ssalbdivad :)

Btw, I'm happy to help more on the PR/issue queue if that’s useful. Just say the word!

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

Labels

None yet

Projects

Status: Done (merged or closed)

Development

Successfully merging this pull request may close these issues.

[attest] ParseError: "A Scope already named Array already exists" when used with arktype@2.2.0

2 participants