Skip to content

types (breaking): narrow TermWrapper's Literal/Quad member surface; remove IRdfJsTerm#86

Draft
jeswr wants to merge 1 commit into
rdfjs:mainfrom
jeswr:types/narrow-term-wrapper-surface
Draft

types (breaking): narrow TermWrapper's Literal/Quad member surface; remove IRdfJsTerm#86
jeswr wants to merge 1 commit into
rdfjs:mainfrom
jeswr:types/narrow-term-wrapper-surface

Conversation

@jeswr

@jeswr jeswr commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

What this does

TermWrapper currently declares every member of every RDF/JS term type, so language, datatype, direction, subject, predicate, object and graph appear to exist on every wrapper — including wrappers of term types that do not have them. As raised in the #61 discussion, these are false types: language is declared to be defined even on a wrapped NamedNode, where it is undefined at runtime.

This PR narrows the declared type surface to the members common to all term types (termType, value, equals):

  • Deletes src/type/IRdfJsTerm.ts (internal, never exported from mod.ts).
  • Removes the seven Literal/Quad getters from the class body and re-defines them on TermWrapper.prototype via an Object.defineProperty loop. Runtime behaviour is unchanged (same delegation to the wrapped term, same non-enumerable configurable accessors); they simply no longer appear in the declared type or the emitted .d.ts.
  • Adds an optional type parameter TermWrapper<T extends Term = Term> that narrows termType to that of the wrapped term (e.g. TermWrapper<Literal> has termType: "Literal").
  • Fallout, per the wip/term wrapper type #74 diff: ensureDatatype/ensureListRoot and TermFrom.instance now take TermWrapper; ensure.ts and LiteralAs.ts cast to Literal internally where they read language/datatype.
  • Tests: a new test/unit/term_wrapper_term_members.test.ts covers runtime delegation of all seven members (literal, quad, subclass, and absent-member cases) and guards the type surface with @ts-expect-error assertions and a termType narrowing check.

BREAKING

Consumer code that reads a term-type-specific member off a wrapper (e.g. wrapper.language) no longer type-checks. Narrow to the term type that declares it (e.g. (wrapper as unknown as Literal).language), or — once #83 lands — hold the intersection type returned by TermWrapper.from, which exposes these members on the Literal/Quad side of the intersection without casts. Runtime behaviour is unchanged.

Relationship to #74, #83 and #61

Given the ongoing type-direction discussion on #61/#74, this is deliberately a draft and should not merge before that discussion resolves.

Review timing: This draft was prepared with Claude; I (@jeswr) will personally review it before it progresses. I'm currently batching a lot of work in flight, so expect active review Wednesday-Friday (8-10 July).

Removes the Literal members (language, direction, datatype) and the
Quad members (subject, predicate, object, graph) from TermWrapper's
declared type and deletes the IRdfJsTerm interface. The members remain
available at runtime through prototype-defined getters that delegate to
the wrapped term, so runtime behaviour is unchanged. A new optional
type parameter narrows termType to that of the wrapped term.

BREAKING CHANGE: reading a term-type-specific member such as language
or datatype off a wrapper now requires narrowing to the term type that
declares it (e.g. a cast to Literal).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant