Skip to content

Commit cfddc22

Browse files
Overhaul mapping API (#37)
* Introduce errors * Simplify mapping from value to term 1. It need not produce a wrapper but only a term, because it's used in a context that already has dataset & factory. 2. It need not support undefined returns, because those are handled elsewhere in the transformation pipeline. * Overhaul mapping values to terms * Overhaul mapping terms to values * Don't reference file direct * Use new nomenclature * Fix unrelated test * Accommodate stricter mapping * Partially cover * XSD NS * Mitigate binary conversion in Node 24 * Apply new mapper naming everywhere Co-authored-by: Matthieu Bosquet <matthieubosquet@gmail.com> * Remove DS param from `ITermFrom` * Explode `TermFrom` & `TermAs` by term type Co-authored-by: Matthieu Bosquet <matthieubosquet@gmail.com> * Consistent guards in TermAs * Tidy --------- Co-authored-by: Matthieu Bosquet <matthieubosquet@gmail.com>
1 parent 1bd8f75 commit cfddc22

34 files changed

Lines changed: 602 additions & 250 deletions

src/ListItem.ts

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,30 @@
11
import { TermWrapper } from "./TermWrapper.js"
22
import type { DataFactory, DatasetCore, Term } from "@rdfjs/types"
3-
import { ValueMapping } from "./mapping/ValueMapping.js"
4-
import { TermMapping } from "./mapping/TermMapping.js"
53
import { RDF } from "./vocabulary/RDF.js"
6-
import type { IValueMapping } from "./type/IValueMapping"
7-
import type { ITermMapping } from "./type/ITermMapping.js"
4+
import type { ITermAsValueMapping } from "./type/ITermAsValueMapping"
5+
import type { ITermFromValueMapping } from "./type/ITermFromValueMapping.js"
6+
import { TermAs } from "./mapping/TermAs.js"
7+
import { TermFrom } from "./mapping/TermFrom.js"
88

99
export class ListItem<T> extends TermWrapper {
10-
constructor(term: Term, dataset: DatasetCore, factory: DataFactory, private readonly valueMapping: IValueMapping<T>, private readonly termMapping: ITermMapping<T>) {
10+
constructor(term: Term, dataset: DatasetCore, factory: DataFactory, private readonly termAs: ITermAsValueMapping<T>, private readonly termFrom: ITermFromValueMapping<T>) {
1111
super(term, dataset, factory)
1212
}
1313

1414
public get firstRaw(): Term | undefined {
15-
return this.singularNullable(RDF.first, ValueMapping.asIs)
15+
return this.singularNullable(RDF.first, TermAs.term)
1616
}
1717

1818
public set firstRaw(value: Term | undefined) {
19-
this.overwriteNullable(RDF.first, value, TermMapping.asIs)
19+
this.overwriteNullable(RDF.first, value, TermFrom.itself)
2020
}
2121

2222
public get restRaw(): Term | undefined {
23-
return this.singularNullable(RDF.rest, ValueMapping.asIs)
23+
return this.singularNullable(RDF.rest, TermAs.term)
2424
}
2525

2626
public set restRaw(value: Term | undefined) {
27-
this.overwriteNullable(RDF.rest, value, TermMapping.asIs)
27+
this.overwriteNullable(RDF.rest, value, TermFrom.itself)
2828
}
2929

3030
public get isListItem(): boolean {
@@ -36,19 +36,19 @@ export class ListItem<T> extends TermWrapper {
3636
}
3737

3838
public get first(): T {
39-
return this.singular(RDF.first, this.valueMapping)
39+
return this.singular(RDF.first, this.termAs)
4040
}
4141

4242
public set first(value: T) {
43-
this.overwrite(RDF.first, value, this.termMapping)
43+
this.overwrite(RDF.first, value, this.termFrom)
4444
}
4545

4646
public get rest(): ListItem<T> {
47-
return this.singular(RDF.rest, w => new ListItem(w as Term, w.dataset, w.factory, this.valueMapping, this.termMapping))
47+
return this.singular(RDF.rest, w => new ListItem(w as Term, w.dataset, w.factory, this.termAs, this.termFrom))
4848
}
4949

5050
public set rest(value: ListItem<T>) {
51-
this.overwrite(RDF.rest, value, TermMapping.identity)
51+
this.overwrite(RDF.rest, value, TermFrom.instance)
5252
}
5353

5454
public pop(): T {

src/Overwriter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { TermWrapper } from "./TermWrapper.js"
22
import { ListItem } from "./ListItem.js"
3-
import { TermMapping } from "./mapping/TermMapping.js"
43
import type { Term } from "@rdfjs/types"
4+
import { TermFrom } from "./mapping/TermFrom.js"
55

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

1111
set listNode(object: ListItem<T>) {
12-
this.overwrite(this.p, object, TermMapping.identity)
12+
this.overwrite(this.p, object, TermFrom.instance)
1313
}
1414
}

src/RdfList.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TermWrapper } from "./TermWrapper.js"
2-
import type { IValueMapping } from "./type/IValueMapping.js"
3-
import type { ITermMapping } from "./type/ITermMapping.js"
2+
import type { ITermAsValueMapping } from "./type/ITermAsValueMapping.js"
3+
import type { ITermFromValueMapping } from "./type/ITermFromValueMapping.js"
44
import { IndexerInterceptor } from "./IndexerInterceptor.js"
55
import { ListItem } from "./ListItem.js"
66
import { RDF } from "./vocabulary/RDF.js"
@@ -10,8 +10,8 @@ import { Overwriter } from "./Overwriter.js"
1010
export class RdfList<T> implements Array<T> {
1111
private root: ListItem<T>
1212

13-
constructor(root: Term, private readonly subject: TermWrapper, private readonly predicate: string, private readonly valueMapping: IValueMapping<T>, private readonly termMapping: ITermMapping<T>) {
14-
this.root = new ListItem(root, this.subject.dataset, this.subject.factory, valueMapping, termMapping)
13+
constructor(root: Term, private readonly subject: TermWrapper, private readonly predicate: string, private readonly termAs: ITermAsValueMapping<T>, private readonly termFrom: ITermFromValueMapping<T>) {
14+
this.root = new ListItem(root, this.subject.dataset, this.subject.factory, termAs, termFrom)
1515

1616
// TODO: Singleton interceptor?
1717
return new Proxy(this, new IndexerInterceptor<T>)
@@ -120,7 +120,7 @@ export class RdfList<T> implements Array<T> {
120120

121121
for (const item of items) {
122122
// A node will be needed either to replace rdf:nil in an empty list or to add a new one to the end of an existing list
123-
const newNode = new ListItem(this.subject.factory.blankNode(), this.subject.dataset, this.subject.factory, this.valueMapping, this.termMapping)
123+
const newNode = new ListItem(this.subject.factory.blankNode(), this.subject.dataset, this.subject.factory, this.termAs, this.termFrom)
124124

125125
const lastNode = this.root.isNil ?
126126
// The statement representing an empty list is replaced by a new one whose object is the new node
@@ -188,7 +188,7 @@ export class RdfList<T> implements Array<T> {
188188
unshift(...items: T[]): number {
189189
for (const item of items.reverse()) {
190190
const firstNode = this.root
191-
this.root = new Overwriter<T>(this.subject, this.predicate).listNode = new ListItem(this.subject.factory.blankNode(), this.subject.dataset, this.subject.factory, this.valueMapping, this.termMapping)
191+
this.root = new Overwriter<T>(this.subject, this.predicate).listNode = new ListItem(this.subject.factory.blankNode(), this.subject.dataset, this.subject.factory, this.termAs, this.termFrom)
192192
this.root.first = item
193193
this.root.rest = firstNode
194194
}

src/TermWrapper.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Quad_Object, Quad_Subject, Term } from "@rdfjs/types"
2-
import type { ITermMapping } from "./type/ITermMapping.js"
3-
import type { IValueMapping } from "./type/IValueMapping.js"
2+
import type { ITermFromValueMapping } from "./type/ITermFromValueMapping.js"
3+
import type { ITermAsValueMapping } from "./type/ITermAsValueMapping.js"
44
import { WrappingSet } from "./WrappingSet.js"
55
import { WrappingMap } from "./WrappingMap.js"
66
import { AnyTermWithContext } from "./AnyTermWithContext.js"
@@ -10,7 +10,7 @@ export class TermWrapper extends AnyTermWithContext {
1010
return this.constructor.name
1111
}
1212

13-
protected singular<T>(p: string, valueMapping: IValueMapping<T>): T {
13+
protected singular<T>(p: string, termAs: ITermAsValueMapping<T>): T {
1414
const predicate = this.factory.namedNode(p)
1515
const matches = this.dataset.match(this as Term, predicate)[Symbol.iterator]()
1616

@@ -25,28 +25,28 @@ export class TermWrapper extends AnyTermWithContext {
2525
throw new Error(`More than one value for predicate ${p} on term ${this.value}`)
2626
}
2727

28-
return valueMapping(new TermWrapper(first.object, this.dataset, this.factory))
28+
return termAs(new TermWrapper(first.object, this.dataset, this.factory))
2929
}
3030

31-
protected singularNullable<T>(p: string, valueMapping: IValueMapping<T>): T | undefined {
31+
protected singularNullable<T>(p: string, termAs: ITermAsValueMapping<T>): T | undefined {
3232
const predicate = this.factory.namedNode(p)
3333

3434
for (const q of this.dataset.match(this as Term, predicate)) {
35-
return valueMapping(new TermWrapper(q.object, this.dataset, this.factory))
35+
return termAs(new TermWrapper(q.object, this.dataset, this.factory))
3636
}
3737

3838
return
3939
}
4040

41-
protected overwrite<T>(p: string, value: T, nodeMapping: ITermMapping<T>): void {
41+
protected overwrite<T>(p: string, value: T, termFrom: ITermFromValueMapping<T>): void {
4242
if (value === undefined) {
4343
throw new Error("value cannot be undefined")
4444
}
4545

46-
this.overwriteNullable(p, value, nodeMapping)
46+
this.overwriteNullable(p, value, termFrom)
4747
}
4848

49-
protected overwriteNullable<T>(p: string, value: T | undefined, termMapping: ITermMapping<T>): void {
49+
protected overwriteNullable<T>(p: string, value: T | undefined, termFrom: ITermFromValueMapping<T>): void {
5050
const predicate = this.factory.namedNode(p)
5151

5252
for (const q of this.dataset.match(this as Term, predicate)) {
@@ -65,7 +65,7 @@ export class TermWrapper extends AnyTermWithContext {
6565
}
6666

6767
// TODO: TermMapping undefined: the term mapping is not invoked if undefined
68-
const o = termMapping(value, this.dataset, this.factory)
68+
const o = termFrom(value, this.factory)
6969

7070
if (o === undefined) {
7171
return // TODO: throw error?
@@ -79,12 +79,12 @@ export class TermWrapper extends AnyTermWithContext {
7979
this.dataset.add(q)
8080
}
8181

82-
protected objects<T>(p: string, valueMapping: IValueMapping<T>, termMapping: ITermMapping<T>): Set<T> {
83-
return new WrappingSet(this, p, valueMapping, termMapping)
82+
protected objects<T>(p: string, termAs: ITermAsValueMapping<T>, termFrom: ITermFromValueMapping<T>): Set<T> {
83+
return new WrappingSet(this, p, termAs, termFrom)
8484
}
8585

86-
protected map<TKey, TValue>(p: string, valueMapping: IValueMapping<[TKey, TValue]>, termMapping: ITermMapping<[TKey, TValue]>): Map<TKey, TValue> {
87-
return new WrappingMap(this, p, valueMapping, termMapping)
86+
protected map<TKey, TValue>(p: string, termAs: ITermAsValueMapping<[TKey, TValue]>, termFrom: ITermFromValueMapping<[TKey, TValue]>): Map<TKey, TValue> {
87+
return new WrappingMap(this, p, termAs, termFrom)
8888
}
8989

9090
private static isQuadSubject(term: Term): term is Quad_Subject {

src/WrappingMap.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import { TermWrapper } from "./TermWrapper.js"
2-
import type { IValueMapping } from "./type/IValueMapping.js"
3-
import type { ITermMapping } from "./type/ITermMapping.js"
2+
import type { ITermAsValueMapping } from "./type/ITermAsValueMapping.js"
3+
import type { ITermFromValueMapping } from "./type/ITermFromValueMapping.js"
44
import type { Quad, Quad_Object, Quad_Subject, Term } from "@rdfjs/types"
55

66
export class WrappingMap<TKey, TValue> implements Map<TKey, TValue> {
7-
constructor(private readonly subject: TermWrapper, private readonly predicate: string, private readonly valueMapping: IValueMapping<[TKey, TValue]>, private readonly termMapping: ITermMapping<[TKey, TValue]>) {
7+
constructor(private readonly subject: TermWrapper, private readonly predicate: string, private readonly termAs: ITermAsValueMapping<[TKey, TValue]>, private readonly termFrom: ITermFromValueMapping<[TKey, TValue]>) {
88
}
99

1010
clear(): void {
@@ -25,7 +25,7 @@ export class WrappingMap<TKey, TValue> implements Map<TKey, TValue> {
2525
this.subject.factory.quad(
2626
this.subject as Quad_Subject,
2727
p,
28-
this.termMapping(entry, this.subject.dataset, this.subject.factory) as Quad_Object))
28+
this.termFrom(entry, this.subject.factory) as Quad_Object))
2929

3030
return true
3131
}
@@ -72,7 +72,7 @@ export class WrappingMap<TKey, TValue> implements Map<TKey, TValue> {
7272

7373
* entries(): MapIterator<[TKey, TValue]> {
7474
for (const quad of this.matches) {
75-
yield this.valueMapping(new TermWrapper(quad.object, this.subject.dataset, this.subject.factory))
75+
yield this.termAs(new TermWrapper(quad.object, this.subject.dataset, this.subject.factory))
7676
}
7777
}
7878

@@ -109,6 +109,6 @@ export class WrappingMap<TKey, TValue> implements Map<TKey, TValue> {
109109
this.subject.factory.quad(
110110
this.subject as Quad_Subject,
111111
p,
112-
this.termMapping([k, v], this.subject.dataset, this.subject.factory) as Quad_Object))
112+
this.termFrom([k, v], this.subject.factory) as Quad_Object))
113113
}
114114
}

src/WrappingSet.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { IValueMapping } from "./type/IValueMapping.js"
2-
import type { ITermMapping } from "./type/ITermMapping.js"
1+
import type { ITermAsValueMapping } from "./type/ITermAsValueMapping.js"
2+
import type { ITermFromValueMapping } from "./type/ITermFromValueMapping.js"
33
import type { DatasetCore, Quad, Quad_Object, Quad_Subject, Term } from "@rdfjs/types"
44
import { TermWrapper } from "./TermWrapper.js"
55

66
export class WrappingSet<T> implements Set<T> {
77
// TODO: Direction
8-
public constructor(private readonly subject: TermWrapper, private readonly predicate: string, private readonly valueMapping: IValueMapping<T>, private readonly termMapping: ITermMapping<T>) {
8+
public constructor(private readonly subject: TermWrapper, private readonly predicate: string, private readonly termAs: ITermAsValueMapping<T>, private readonly termFrom: ITermFromValueMapping<T>) {
99
}
1010

1111
add(value: T): this {
@@ -24,7 +24,7 @@ export class WrappingSet<T> implements Set<T> {
2424
return false
2525
}
2626

27-
const o = this.termMapping(value, this.subject.dataset, this.subject.factory) // TODO: guards
27+
const o = this.termFrom(value, this.subject.factory) // TODO: guards
2828
const p = this.subject.factory.namedNode(this.predicate)
2929

3030
for (const q of this.subject.dataset.match(this.subject as Term, p, o as Term)) {
@@ -64,7 +64,7 @@ export class WrappingSet<T> implements Set<T> {
6464

6565
* values(): SetIterator<T> {
6666
for (const q of this.matches) {
67-
yield this.valueMapping(new TermWrapper(q.object, this.subject.dataset, this.subject.factory))
67+
yield this.termAs(new TermWrapper(q.object, this.subject.dataset, this.subject.factory))
6868
}
6969
}
7070

@@ -75,7 +75,7 @@ export class WrappingSet<T> implements Set<T> {
7575
private quad(value: T): Quad {
7676
const s = this.subject as Quad_Subject // TODO: guard
7777
const p = this.subject.factory.namedNode(this.predicate)
78-
const o = this.termMapping(value, this.subject.dataset, this.subject.factory) as Quad_Object // TODO: guards
78+
const o = this.termFrom(value, this.subject.factory) as Quad_Object // TODO: guards
7979
const q = this.subject.factory.quad(s, p, o)
8080
return q
8181
}

src/decorators/getter.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
import type { ITermMapping } from "../type/ITermMapping.js"
2-
import type { IValueMapping } from "../type/IValueMapping.js"
1+
import type { ITermFromValueMapping } from "../type/ITermFromValueMapping.js"
2+
import type { ITermAsValueMapping } from "../type/ITermAsValueMapping.js"
33
import type { TermWrapper } from "../TermWrapper.js"
44

55
import { GetterArity } from "./GetterArity.js"
6-
import { TermMapping } from "../mapping/TermMapping.js"
6+
import { LiteralFrom } from "../mapping/LiteralFrom.js"
77

8-
export function getter(predicate: string, getterArity: GetterArity, valueMapping: IValueMapping<any>, termMapping: ITermMapping<any> = TermMapping.stringToLiteral): any {
8+
export function getter(predicate: string, getterArity: GetterArity, termAs: ITermAsValueMapping<any>, termFrom: ITermFromValueMapping<any> = LiteralFrom.string): any {
99
return function (target: any, context: ClassGetterDecoratorContext): any {
1010
return function (this: TermWrapper): any {
1111
switch (getterArity) {
1212
case GetterArity.Singular:
13-
return this.singular(predicate, valueMapping)
13+
return this.singular(predicate, termAs)
1414
case GetterArity.SingularNullable:
15-
return this.singularNullable(predicate, valueMapping)
15+
return this.singularNullable(predicate, termAs)
1616
case GetterArity.Set:
17-
return this.objects(predicate, valueMapping, termMapping)
17+
return this.objects(predicate, termAs, termFrom)
1818
}
1919
}
2020
}

src/decorators/setter.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import type { ITermMapping } from "../type/ITermMapping.js"
1+
import type { ITermFromValueMapping } from "../type/ITermFromValueMapping.js"
22
import type { TermWrapper } from "../TermWrapper.js"
33
import { SetterArity } from "./SetterArity.js"
44

5-
export function setter(predicate: string, setterArity: SetterArity, termMapping: ITermMapping<any>): any {
5+
export function setter(predicate: string, setterArity: SetterArity, termFrom: ITermFromValueMapping<any>): any {
66
return function (target: any, context: ClassSetterDecoratorContext): any {
77
return function (this: TermWrapper, value: any): void {
88
switch (setterArity) {
99
case SetterArity.Singular:
10-
this.overwrite(predicate, value, termMapping)
10+
this.overwrite(predicate, value, termFrom)
1111
break
1212
case SetterArity.SingularNullable:
13-
this.overwriteNullable(predicate, value, termMapping)
13+
this.overwriteNullable(predicate, value, termFrom)
1414
break
1515
}
1616
}

src/errors/LiteralDatatypeError.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { TermError } from "./TermError.js"
2+
import type { Term } from "@rdfjs/types"
3+
4+
export class LiteralDatatypeError extends TermError {
5+
constructor(term: Term, public readonly datatypes: Iterable<string>, cause?: any) {
6+
super(term, `Literal datatype must be one of ${[...datatypes].map(d => `<${d}>`).join(", ")}`, cause)
7+
}
8+
}

src/errors/TermError.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { WrapperError } from "./WrapperError.js"
2+
import type { Term } from "@rdfjs/types"
3+
4+
export class TermError extends WrapperError {
5+
constructor(public readonly term: Term, message?: string, cause?: any) {
6+
super(message, cause)
7+
}
8+
}

0 commit comments

Comments
 (0)