Skip to content

feat: DatasetWrapper helpers return T & Quad_Subject / T & Quad_Object - #87

Draft
jeswr wants to merge 2 commits into
rdfjs:mainfrom
jeswr:feat/dataset-wrapper-typed-helpers
Draft

feat: DatasetWrapper helpers return T & Quad_Subject / T & Quad_Object#87
jeswr wants to merge 2 commits into
rdfjs:mainfrom
jeswr:feat/dataset-wrapper-typed-helpers

Conversation

@jeswr

@jeswr jeswr commented Jul 6, 2026

Copy link
Copy Markdown
Member

Types the results of the DatasetWrapper navigation helpers as both the mapping class and the matched term.

What it does

  • subjectsOf, instancesOf and matchSubjectsOf now return Iterable<T & Quad_Subject>, and objectsOf and matchObjectsOf return Iterable<T & Quad_Object>, with the generics constrained to T extends TermWrapper. Results can be passed directly anywhere an RDF/JS term is expected — factory.quad(…), dataset.match(…) — without casts, and the compiler rejects impossible uses (a matched subject as a Literal, a matched object as a Quad_Subject, …).
  • The helpers construct results with the TermWrapper.from static factory (feat: TermWrapper.from static factory with intersection return type #83) instead of new, which is what carries the matched term's type into the instance type.
  • ITermWrapperConstructor<T> is tightened to the static side of a TermWrapper subclass: the familiar constructor signature intersected with Pick<typeof TermWrapper, "from">, with T constrained (and defaulted) to TermWrapper. Any class deriving from TermWrapper satisfies it automatically because the static from is inherited; classes that merely copy the constructor signature no longer do.
  • TermAs.instance picks up the matching T extends TermWrapper constraint.

Runtime behaviour is unchanged: from delegates to the constructor.

Stacked on #83

The helpers call TermWrapper.from, so this branch includes #83's commit; until #83 merges, it shows up in this diff too. Reviewing/merging #83 first reduces this PR to the single DatasetWrapper/ITermWrapperConstructor commit.

Relation to #61/#74

Supersedes the dataset-typing slice of #61: the DatasetWrapper helper signatures, the ITermWrapperConstructor tightening, and its test/unit/dataset_wrapper_types.test.ts. One deliberate difference: #61's version of that test asserted that Literal/Quad members (.language, .subject, …) do not exist on the results, which relies on #61's restructuring of the TermWrapper getters — the contested slice that #85 deliberately leaves out. On top of the current TermWrapper those members remain present on every wrapper, so the ported test instead pins down exactly what this slice adds: results are assignable to Quad_Subject/Quad_Object without casts, and @ts-expect-error proves they are not Literal/Quad_Predicate/Quad_Subject where the position rules that out. Should the getter restructuring land later, strengthening these tests to #61's assertions is a small follow-up.

Testing

  • New test/unit/dataset_wrapper_types.test.ts: runtime assertions for all five helpers (result counts, mapping-class members) plus compile-level assertions checked by tsc -p test — positive (results used as subject/object terms in factory.quad and match without casts) and negative (@ts-expect-error for invalid positions; ITermWrapperConstructor satisfied by the Parent/Child models, rejected for a structurally similar class that does not derive from TermWrapper).
  • test/unit/model/ParentDataset.ts getters updated to the intersection return types (covariant, so the existing dataset_wrapper.test.ts is unchanged).
  • npm test: 149 tests, 0 fail (5 pre-existing skips).
  • npx typedoc: 0 errors, 6 warnings — identical to main.
  • npm audit --omit=dev --audit-level=moderate: 0 vulnerabilities.

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).

jeswr and others added 2 commits July 6, 2026 00:25
Adds an overloaded public static from(term, dataset, factory) to
TermWrapper that is runtime-equivalent to the constructor but returns
the intersection of the (sub)class instance type and the RDF/JS term
type of the wrapped term (NamedNode when a string IRI is passed, the
inferred term type otherwise). Instances created this way can be passed
directly to factory.quad(), dataset.match() and any other RDF/JS Term
position without casts.

Fully non-breaking: the constructor and the existing typed getters are
untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…helpers

Constrains the generics of subjectsOf/objectsOf/instancesOf/
matchSubjectsOf/matchObjectsOf to T extends TermWrapper and intersects
the results with the matched term type, so they can be used anywhere an
RDF/JS term is expected without casts. Results are constructed via the
TermWrapper.from static factory, which carries the term type into the
instance type. ITermWrapperConstructor is tightened to the static side
of a TermWrapper subclass (constructor signature plus the inherited
from factory) and TermAs.instance picks up the matching constraint.

Runtime behaviour is unchanged.

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