Skip to content

feat(schema)!: per-module language namespace in can:// ids - #115

Merged
rahlk merged 2 commits into
mainfrom
feat/issue-114-js-namespace
Aug 31, 2026
Merged

feat(schema)!: per-module language namespace in can:// ids#115
rahlk merged 2 commits into
mainfrom
feat/issue-114-js-namespace

Conversation

@rahlk

@rahlk rahlk commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Closes #114. Spec: docs/design/specs/js-language-namespace.md (committed in this PR).

src/schema/ids.ts hardcoded const LANGUAGE = "typescript" and every code id descended from it,
so a JavaScript module emitted can://typescript/<app>/lib/foo.js/fn. The first can:// segment is
defined as a language namespace, and for .js modules it named the wrong language. Measured on
nodejs/node/lib: all 406 pure-JavaScript modules read can://typescript/….

can://typescript/mix                              :Application anchor, unchanged
can://typescript/mix/src/index.ts/greet           TypeScript module
can://javascript/mix/mocks/dpapi.js/defaultDpapi  JavaScript module
can://artifact/mix/package.json                   artifact layer, still neutral

moduleIdOf now takes the app name and derives the namespace from the file key:
.js/.jsx/.mjs/.cjs are javascript, everything else typescript. assignIds.ts is the only
caller, so the change is contained to one construction site.

BREAKING

JavaScript ids change. Neo4j MERGE keys on id, so re-projecting creates new nodes for JS
modules instead of updating existing ones — a store built by an earlier version needs a rebuild, and
saved Cypher or persisted references to JS node ids must be updated.

The analysis cache is unaffected: ids are stamped per run because they embed --app-name, and
the cached tree is id-free.

The accepted inconsistency

The :Application anchor keeps saying typescript while owning javascript children. Chosen
knowingly over the alternatives: a neutral anchor (can://app/<app>, mirroring the artifact layer)
is the more coherent end state but moves every id in every projection rather than only JS ones,
and two anchors would break the single-:Application invariant that carries analyzer identity
(#43). A mixed repository has no single language, so any single-anchor scheme names one language or
none. Moving to a neutral anchor stays open as a follow-up.

Verification

  • 237 pass / 6 skip / 0 fail (+11 from test/ids.test.ts); typecheck, build, and gen:schema
    all clean.
  • The change surfaced itself: two existing assertions in test/checker-guard.test.ts failed because
    they hardcoded can://typescript/… for a .js module. That fixture holds both a .ts and a
    .js module, so it now serves as the two-namespaces-in-one-run assertion.
  • Edge cases pinned: .d.ts resolves to typescript (not read as a .ts suffix on a .d file),
    and the regex is anchored so a directory named vendor.js does not make its .ts children
    JavaScript.
  • Neo4j projection over a mixed fixture emits all three namespaces — can://artifact/,
    can://javascript/, can://typescript/.

rahlk added 2 commits August 31, 2026 15:03
ids.ts hardcoded LANGUAGE = "typescript" and every code id descended from it,
so a JavaScript module emitted can://typescript/<app>/lib/foo.js/fn. The first
can:// segment is a language namespace, and for .js modules it named the wrong
language -- on nodejs/node/lib, 406 pure-JavaScript modules all read
can://typescript/.

moduleIdOf now takes the app NAME and derives the namespace from the file key:
.js/.jsx/.mjs/.cjs are javascript, everything else typescript. The match is
anchored, so a directory named vendor.js does not change its .ts children, and
.d.ts stays typescript rather than being read as a .ts suffix on a .d file.

BREAKING for JavaScript ids: Neo4j MERGE keys on id, so re-projecting creates
new nodes for JS modules instead of updating existing ones. A store built by an
earlier version needs a rebuild, and saved queries referencing JS node ids must
be updated. The analysis cache is unaffected -- ids are stamped per run because
they embed --app-name, and the cached tree is id-free.

The :Application anchor deliberately keeps typescript while owning javascript
children. A mixed repository has no single language, so any single-anchor scheme
names one language or none; a neutral anchor would move every id in every
projection, and two anchors would break the single-anchor invariant (#43).

Closes #114. Spec: docs/design/specs/js-language-namespace.md
@rahlk
rahlk merged commit 234895e into main Aug 31, 2026
1 check passed
@rahlk
rahlk deleted the feat/issue-114-js-namespace branch August 31, 2026 21:21
@rahlk rahlk mentioned this pull request Sep 3, 2026
rahlk added a commit that referenced this pull request Sep 3, 2026
ANALYZER_VERSION moves with package.json because it is the cache-invalidation key
(utils/cache.ts): this release changes artifact text capture (#117) and per-module
id namespacing (#115), so a warm 1.1.0 cache must not be reused against it.

Neo4j schema contract is 2.0.0 as of #144 — collapsed back from 2.1.0, since
intermediate 2.x versions are not meaningful until every analyzer re-baselines
together.

Released as a MINOR despite three breaking-marked commits (#115, #117, #144), by
explicit decision. The release notes lead with those breaks and their migrations.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

can:// ids label JavaScript modules as typescript — split the language namespace

1 participant