Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ import { LiteralAs, LiteralFrom, OptionalAs, OptionalFrom, TermWrapper } from "h

class Person extends TermWrapper {
get name() {
return OptionalFrom.subjectPredicate(this, "https://example.org/name", LiteralAs.string)
return OptionalFrom.subjectPredicate(this.node, "https://example.org/name", LiteralAs.string)
}

set name(value) {
OptionalAs.object(this, "https://example.org/name", value, LiteralFrom.string)
OptionalAs.object(this.node, "https://example.org/name", value, LiteralFrom.string)
}
}
```
Expand All @@ -57,7 +57,7 @@ ex:person1 ex:name "Alice" .
Class usage:

```javascript
const person1 = new Person("https://example.org/person1", dataset_x, DataFactory)
const person1 = Person.from("https://example.org/person1", dataset_x, DataFactory)

// Get property
console.log(person1.name)
Expand Down Expand Up @@ -116,19 +116,19 @@ import { LiteralAs, LiteralFrom, OptionalAs, OptionalFrom, TermAs, TermFrom, Ter

class Person extends TermWrapper {
get name() {
return OptionalFrom.subjectPredicate(this, "https://example.org/name", LiteralAs.string)
return OptionalFrom.subjectPredicate(this.node, "https://example.org/name", LiteralAs.string)
}

set name(value) {
OptionalAs.object(this, "https://example.org/name", value, LiteralFrom.string)
OptionalAs.object(this.node, "https://example.org/name", value, LiteralFrom.string)
}

get mum() {
return OptionalFrom.subjectPredicate(this, "https://example.org/mum", TermAs.instance(Person))
return OptionalFrom.subjectPredicate(this.node, "https://example.org/mum", TermAs.instance(Person))
}

set mum(value) {
OptionalAs.object(this, "https://example.org/mum", value, TermFrom.instance)
OptionalAs.object(this.node, "https://example.org/mum", value, TermFrom.instance)
}
}
```
Expand All @@ -149,7 +149,7 @@ ex:person2
Class usage:

```javascript
const person2 = new Person("https://example.org/person2", dataset_z, DataFactory)
const person2 = Person.from("https://example.org/person2", dataset_z, DataFactory)

// Get property
console.log(person2.name)
Expand All @@ -160,7 +160,7 @@ console.log(person2.mum.name)
// outputs "Alice"

// Set class properties
const person3 = new Person("https://example.org/person3", dataset_z, DataFactory)
const person3 = Person.from("https://example.org/person3", dataset_z, DataFactory)
person3.name = "Joanne"
person1.mum = person3
console.log(person1.mum.name)
Expand Down
17 changes: 9 additions & 8 deletions src/DatasetWrapper.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import type { DataFactory, DatasetCore, Quad, Quad_Graph, Term } from "@rdfjs/types"
import type { DataFactory, DatasetCore, Quad, Quad_Graph, Quad_Object, Quad_Subject, Term } from "@rdfjs/types"
import type { ITermWrapperConstructor } from "./type/ITermWrapperConstructor.js"
import type { NamedGraphDataset } from "./NamedGraphDataset.js"
import type { INamedGraphDatasetConstructor } from "./type/INamedGraphDatasetConstructor.js"

import { RDF } from "./vocabulary/RDF.js"
import { TermWrapper } from "./TermWrapper.js"

export class DatasetWrapper implements DatasetCore {
//#region DatasetCore
Expand Down Expand Up @@ -41,15 +42,15 @@ export class DatasetWrapper implements DatasetCore {

//#region Utilities

protected subjectsOf<T>(predicate: string, termWrapper: ITermWrapperConstructor<T>): Iterable<T> {
protected subjectsOf<T extends TermWrapper>(predicate: string, termWrapper: ITermWrapperConstructor<T>): Iterable<T & Quad_Subject> {
return this.matchSubjectsOf(termWrapper, this.factory.namedNode(predicate))
}

protected objectsOf<T>(predicate: string, termWrapper: ITermWrapperConstructor<T>): Iterable<T> {
protected objectsOf<T extends TermWrapper>(predicate: string, termWrapper: ITermWrapperConstructor<T>): Iterable<T & Quad_Object> {
return this.matchObjectsOf(termWrapper, undefined, this.factory.namedNode(predicate))
}

protected instancesOf<T>(klass: string, constructor: ITermWrapperConstructor<T>): Iterable<T> {
protected instancesOf<T extends TermWrapper>(klass: string, constructor: ITermWrapperConstructor<T>): Iterable<T & Quad_Subject> {
return this.matchSubjectsOf(constructor, this.factory.namedNode(RDF.type), this.factory.namedNode(klass))
}

Expand All @@ -69,15 +70,15 @@ export class DatasetWrapper implements DatasetCore {
return new klass(g, this.dataset, this.factory)
}

protected* matchSubjectsOf<T>(termWrapper: ITermWrapperConstructor<T>, predicate?: Term, object?: Term, graph?: Term): Iterable<T> {
protected* matchSubjectsOf<T extends TermWrapper>(termWrapper: ITermWrapperConstructor<T>, predicate?: Term, object?: Term, graph?: Term): Iterable<T & Quad_Subject> {
for (const q of this.match(undefined, predicate, object, graph)) {
yield new termWrapper(q.subject, this, this.factory)
yield termWrapper.from(q.subject, this, this.factory) as T & Quad_Subject
}
}

protected* matchObjectsOf<T>(termWrapper: ITermWrapperConstructor<T>, subject?: Term, predicate?: Term, graph?: Term): Iterable<T> {
protected* matchObjectsOf<T extends TermWrapper>(termWrapper: ITermWrapperConstructor<T>, subject?: Term, predicate?: Term, graph?: Term): Iterable<T & Quad_Object> {
for (const q of this.match(subject, predicate, undefined, graph)) {
yield new termWrapper(q.object, this, this.factory)
yield termWrapper.from(q.object, this, this.factory) as T & Quad_Object
}
}

Expand Down
17 changes: 9 additions & 8 deletions src/ListItem.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TermWrapper } from "./TermWrapper.js"
import type { TermNode } from "./TermWrapper.js"
import type { DataFactory, DatasetCore, Term } from "@rdfjs/types"
import { RDF } from "./vocabulary/RDF.js"
import type { ITermAsValueMapping } from "./type/ITermAsValueMapping.js"
Expand All @@ -16,19 +17,19 @@ export class ListItem<T> extends TermWrapper {
}

public get firstRaw(): Term | undefined {
return OptionalFrom.subjectPredicate(this, RDF.first, TermAs.term)
return OptionalFrom.subjectPredicate(this.node, RDF.first, TermAs.is)
}

public set firstRaw(value: Term | undefined) {
OptionalAs.object(this, RDF.first, value, TermFrom.itself)
OptionalAs.object(this.node, RDF.first, value, TermFrom.itself)
}

public get restRaw(): Term | undefined {
return OptionalFrom.subjectPredicate(this, RDF.rest, TermAs.term)
return OptionalFrom.subjectPredicate(this.node, RDF.rest, TermAs.is)
}

public set restRaw(value: Term | undefined) {
OptionalAs.object(this, RDF.rest, value, TermFrom.itself)
OptionalAs.object(this.node, RDF.rest, value, TermFrom.itself)
}

public get isListItem(): boolean {
Expand All @@ -40,19 +41,19 @@ export class ListItem<T> extends TermWrapper {
}

public get first(): T {
return RequiredFrom.subjectPredicate(this, RDF.first, this.termAs)
return RequiredFrom.subjectPredicate(this.node, RDF.first, this.termAs)
}

public set first(value: T) {
RequiredAs.object(this, RDF.first, value, this.termFrom)
RequiredAs.object(this.node, RDF.first, value, this.termFrom)
}

public get rest(): ListItem<T> {
return RequiredFrom.subjectPredicate(this, RDF.rest, (w: TermWrapper) => new ListItem(w as Term, w.dataset, w.factory, this.termAs, this.termFrom))
return RequiredFrom.subjectPredicate(this.node, RDF.rest, (w: TermNode) => new ListItem(w, w.dataset, w.factory, this.termAs, this.termFrom))
}

public set rest(value: ListItem<T>) {
RequiredAs.object(this, RDF.rest, value, TermFrom.instance)
RequiredAs.object(this.node, RDF.rest, value, TermFrom.instance)
}

public pop(): T {
Expand Down
5 changes: 3 additions & 2 deletions src/Overwriter.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import { TermWrapper } from "./TermWrapper.js"
import type { TermNode } from "./TermWrapper.js"
import { ListItem } from "./ListItem.js"
import type { Term } from "@rdfjs/types"
import { TermFrom } from "./mapping/TermFrom.js"
import { RequiredAs } from "./mapping/RequiredAs.js"

export class Overwriter<T> extends TermWrapper {
constructor(subject: TermWrapper, private readonly p: string) {
constructor(subject: TermNode, private readonly p: string) {
super(subject as Term, subject.dataset, subject.factory);
}

set listNode(object: ListItem<T>) {
RequiredAs.object(this, this.p, object, TermFrom.instance)
RequiredAs.object(this.node, this.p, object, TermFrom.instance)
}
}
7 changes: 6 additions & 1 deletion src/RdfList.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TermWrapper } from "./TermWrapper.js"
import type { TermNode } from "./TermWrapper.js"
import type { ITermAsValueMapping } from "./type/ITermAsValueMapping.js"
import type { ITermFromValueMapping } from "./type/ITermFromValueMapping.js"
import { IndexerInterceptor } from "./IndexerInterceptor.js"
Expand All @@ -10,7 +11,7 @@ import { Overwriter } from "./Overwriter.js"
export class RdfList<T> implements Array<T> {
private root: ListItem<T>

constructor(root: Term, private readonly subject: TermWrapper, private readonly predicate: string, private readonly termAs: ITermAsValueMapping<T>, private readonly termFrom: ITermFromValueMapping<T>) {
constructor(root: Term, private readonly subject: TermNode, private readonly predicate: string, private readonly termAs: ITermAsValueMapping<T>, private readonly termFrom: ITermFromValueMapping<T>) {
this.root = new ListItem(root, this.subject.dataset, this.subject.factory, termAs, termFrom)

// TODO: Singleton interceptor?
Expand Down Expand Up @@ -209,4 +210,8 @@ export class RdfList<T> implements Array<T> {
private get items(): Iterable<ListItem<T>> {
return this.root.items()
}

public static from<T>(root: Term, subject: TermNode, predicate: string, termAs: ITermAsValueMapping<T>, termFrom: ITermFromValueMapping<T>): RdfList<T> {
return new RdfList(root, subject, predicate, termAs, termFrom);
}
}
83 changes: 45 additions & 38 deletions src/TermWrapper.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import type { BaseQuad, DataFactory, DatasetCore, Literal, NamedNode, Quad_Subject, Term } from "@rdfjs/types"
import type { BaseQuad, BlankNode, DataFactory, DatasetCore, DefaultGraph, Literal, NamedNode, Quad_Subject, Term, Variable } from "@rdfjs/types"
import type { IRdfJsTerm } from "./type/IRdfJsTerm.js"

type ToInterface<A extends Term> =
A extends { termType: "NamedNode" } ? NamedNode :
A extends { termType: "BlankNode" } ? BlankNode :
A extends { termType: "Literal" } ? Literal :
A extends { termType: "Variable" } ? Variable :
A extends { termType: "DefaultGraph" } ? DefaultGraph :
A extends { termType: "Quad" } ? BaseQuad :
Term

type InferTerm<A> = A extends string ? NamedNode : A extends Term ? ToInterface<A> : Term
type WithTerm<W, T extends Term> = W extends TermWrapper<any> ? Omit<W, keyof TermWrapper> & TermWrapper<T> & T : W & T

export type TermNode<T extends Term = Term> = TermWrapper<T> & T

/**
* `TermWrapper` is one of the two central constructs of this library. It is the base class of all models that represent a mapping from RDF to JavaScript. It _is_ an {@link Term | RDF/JS term} (or node) that also has a reference to both the dataset (or graph) that is the context of (i.e. contains) the term and to a factory that can be used to create additional terms.
*
Expand Down Expand Up @@ -69,8 +83,8 @@ import type { IRdfJsTerm } from "./type/IRdfJsTerm.js"
* dataset.match(instance as Term)
* ```
*/
export class TermWrapper implements IRdfJsTerm {
private readonly original: Term
export class TermWrapper<T extends Term = Term> implements IRdfJsTerm {
private readonly original: T
private readonly _dataset: DatasetCore
private readonly _factory: DataFactory

Expand All @@ -93,7 +107,7 @@ export class TermWrapper implements IRdfJsTerm {
constructor(term: Term, dataset: DatasetCore, factory: DataFactory)

constructor(term: string | Term, dataset: DatasetCore, factory: DataFactory) {
this.original = typeof term === "string" ? factory.namedNode(term) : term
this.original = (typeof term === "string" ? factory.namedNode(term) : term) as T
this._dataset = dataset
this._factory = factory
}
Expand Down Expand Up @@ -187,9 +201,7 @@ export class TermWrapper implements IRdfJsTerm {
return this.constructor.name
}

//#region Implementation of RDF/JS Term

get termType(): Term["termType"] {
get termType(): T["termType"] {
return this.original.termType
}

Expand All @@ -201,41 +213,36 @@ export class TermWrapper implements IRdfJsTerm {
return this.original.equals(other)
}

//#region Implementation of RDF/JS Literal

get language(): string {
return (this.original as Literal).language
declare language: string
declare direction: Literal["direction"]
declare datatype: NamedNode
declare subject: Term
declare predicate: Term
declare object: Term
declare graph: Term

public static from<
This extends abstract new (...args: any[]) => TermWrapper,
A extends ConstructorParameters<This>[0]
>(this: This, term: A, dataset: DatasetCore, factory: DataFactory): WithTerm<InstanceType<This>, InferTerm<A>> {
return new (this as any)(term, dataset, factory) as any
}

get direction(): Literal["direction"] {
return (this.original as Literal).direction
public static in<This extends abstract new (...args: any[]) => TermWrapper>(
this: This, dataset: DatasetCore, factory: DataFactory
): <A extends ConstructorParameters<This>[0]>(term: A) => WithTerm<InstanceType<This>, InferTerm<A>> {
return (term) => new (this as any)(term, dataset, factory) as any
}

get datatype(): NamedNode {
return (this.original as Literal).datatype
}

//#endregion

//#region Implementation of RDF/JS Quad

get subject(): Term {
return (this.original as BaseQuad).subject
get node(): WithTerm<this, T> {
return this as any
}
}

get predicate(): Term {
return (this.original as BaseQuad).predicate
}

get object(): Term {
return (this.original as BaseQuad).object
}

get graph(): Term {
return (this.original as BaseQuad).graph
}

//#endregion

//#endregion
for (const prop of ['language', 'direction', 'datatype', 'subject', 'predicate', 'object', 'graph'] as const) {
Object.defineProperty(TermWrapper.prototype, prop, {
get(this: TermWrapper) { return (this as any).original[prop] },
enumerable: false,
configurable: true,
})
}
7 changes: 4 additions & 3 deletions src/WrappingMap.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { TermWrapper } from "./TermWrapper.js"
import type { TermNode } from "./TermWrapper.js"
import type { ITermAsValueMapping } from "./type/ITermAsValueMapping.js"
import type { ITermFromValueMapping } from "./type/ITermFromValueMapping.js"
import type { Quad, Quad_Object, Quad_Subject, Term } from "@rdfjs/types"

export class WrappingMap<TKey, TValue> implements Map<TKey, TValue> {
constructor(private readonly subject: TermWrapper, private readonly predicate: string, private readonly termAs: ITermAsValueMapping<[TKey, TValue]>, private readonly termFrom: ITermFromValueMapping<[TKey, TValue]>) {
constructor(private readonly subject: TermNode, private readonly predicate: string, private readonly termAs: ITermAsValueMapping<[TKey, TValue]>, private readonly termFrom: ITermFromValueMapping<[TKey, TValue]>) {
}

clear(): void {
Expand Down Expand Up @@ -72,7 +73,7 @@ export class WrappingMap<TKey, TValue> implements Map<TKey, TValue> {

* entries(): MapIterator<[TKey, TValue]> {
for (const quad of this.matches) {
yield this.termAs(new TermWrapper(quad.object, this.subject.dataset, this.subject.factory))
yield this.termAs(TermWrapper.from(quad.object, this.subject.dataset, this.subject.factory))
}
}

Expand All @@ -99,7 +100,7 @@ export class WrappingMap<TKey, TValue> implements Map<TKey, TValue> {
private get matches(): Iterable<Quad> {
const p = this.subject.factory.namedNode(this.predicate)

return this.subject.dataset.match(this.subject as Term, p)
return this.subject.dataset.match(this.subject, p)
}

private add(k: TKey, v: TValue) {
Expand Down
Loading
Loading