Skip to content

Commit fd30984

Browse files
langsamujeswr
andauthored
Term wrapper docs (#59)
Co-authored-by: Jesse Wright <63333554+jeswr@users.noreply.github.com>
1 parent 99a6422 commit fd30984

8 files changed

Lines changed: 297 additions & 32 deletions

File tree

src/TermWrapper.ts

Lines changed: 194 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,194 @@
1-
import type { BaseQuad, DataFactory, DatasetCore, Literal, NamedNode, Term } from "@rdfjs/types"
2-
import type { IAnyTerm } from "./type/IAnyTerm.js"
1+
import type { BaseQuad, DataFactory, DatasetCore, Literal, NamedNode, Quad_Subject, Term } from "@rdfjs/types"
2+
import type { IRdfJsTerm } from "./type/IRdfJsTerm.js"
33

4-
export class TermWrapper implements IAnyTerm {
4+
/**
5+
* `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.
6+
*
7+
* @remarks
8+
* This class contains all members of all types derived from {@link Term}. This is so instances of this class can be used _as_ instances of any term type. See relevant example.
9+
*
10+
* @example Basic usage
11+
* The basic pattern of working with this class is to simply extend it and add accessors and mutators (both optional) that expose data from the underlying RDF:
12+
* ```ts
13+
* class SomeClass extends TermWrapper {
14+
* get someProperty(): string {
15+
* return RequiredFrom.subjectPredicate(this, "http://example.com/someProperty", LiteralAs.string)
16+
* }
17+
*
18+
* set someProperty(value: string) {
19+
* RequiredAs.object(this, "http://example.com/someProperty", value, LiteralFrom.string)
20+
* }
21+
* }
22+
* ```
23+
*
24+
* Assume the following RDF data:
25+
* ```turtle
26+
* BASE <http://example.com/>
27+
*
28+
* <someSubject> <someProperty> "some value" .
29+
* ```
30+
*
31+
* We can work with this data in JavaScript and TypeScript as follows:
32+
* ```ts
33+
* const dataset: DatasetCore // which has the RDF above loaded
34+
* const instance = new SomeClass("http://example.com/someSubject", dataset, DataFactory)
35+
*
36+
* const value = instance.someProperty // contains "some value"
37+
*
38+
* instance.someProperty = "some other value" // underlying RDF is now <someSubject> <someProperty> "some other value" .
39+
* ```
40+
*
41+
* @example Using instances of TermWrapper as instances of RDF/JS Term
42+
* Since this class implements all members of all term types (named nodes, literals, blank nodes etc.), it can be cast to an RDF/JS Term:
43+
* ```ts
44+
* let instance: TermWrapper
45+
*
46+
* // Our instance cast as Term
47+
* const term = instance as Term
48+
* ```
49+
*
50+
* @example Using instances of TermWrapper to create quads
51+
* Instances of this class can be used anywhere an RDF/JS Term can be used, which includes creating quads:
52+
* ```ts
53+
* let instance: TermWrapper
54+
* let factory: DataFactory
55+
* const predicate = factory.namedNode("http://example.com/p")
56+
* const object = factory.literal("o")
57+
*
58+
* // Our instance used as subject when creating a quad
59+
* factory.quad(instance as Quad_Subject, predicate, object)
60+
* ```
61+
*
62+
* @example Using instances of TermWrapper to match graph patterns
63+
* Instances of this class can be used anywhere an RDF/JS Term can be used, which includes matching quads in a dataset:
64+
* ```ts
65+
* let instance: TermWrapper
66+
* let dataset: DatasetCore
67+
*
68+
* // Our instance used as subject when matching statements in a dataset
69+
* dataset.match(instance as Term)
70+
* ```
71+
*/
72+
export class TermWrapper implements IRdfJsTerm {
573
private readonly original: Term
74+
private readonly _dataset: DatasetCore
75+
private readonly _factory: DataFactory
676

7-
public constructor(term: string, dataset: DatasetCore, factory: DataFactory)
8-
public constructor(term: Term, dataset: DatasetCore, factory: DataFactory)
9-
public constructor(term: string | Term, public readonly dataset: DatasetCore, public readonly factory: DataFactory) {
77+
/**
78+
* Creates a new instance of {@link TermWrapper}.
79+
*
80+
* @param term The IRI of a named node that is the original term being wrapped.
81+
* @param dataset The dataset that contains the term being wrapped.
82+
* @param factory A collection of methods for creating terms.
83+
*/
84+
constructor(term: string, dataset: DatasetCore, factory: DataFactory)
85+
86+
/**
87+
* Creates a new instance of {@link TermWrapper}.
88+
*
89+
* @param term The original term being wrapped.
90+
* @param dataset The dataset that contains the term being wrapped.
91+
* @param factory A collection of methods for creating terms.
92+
*/
93+
constructor(term: Term, dataset: DatasetCore, factory: DataFactory)
94+
95+
constructor(term: string | Term, dataset: DatasetCore, factory: DataFactory) {
1096
this.original = typeof term === "string" ? factory.namedNode(term) : term
97+
this._dataset = dataset
98+
this._factory = factory
1199
}
12100

101+
/**
102+
* The dataset that contains this term.
103+
*
104+
* This accessor provides access to the underlying RDF graph that is the containing context of a node mapped to JavaScript by instances of this class.
105+
*
106+
* @remarks
107+
* RDF/JS, like many other RDF frameworks, keeps terms and datasets separate. This means that terms do not hold a reference to a dataset they reside in (or were found in). This, in turn, means that a dataset must always be available, separate from the term, if either changes to the underlying data or further traversal of the underlying data is called for. In an object-oriented context however, where property chaining is idiomatic (i.e. `instance.property1.property2`), there is no way to supply the dataset when dereferencing a link in the chain.
108+
*
109+
* This property solves the problem by keeping a reference to the dataset.
110+
*
111+
* @exmaple
112+
* Using the dataset to modify information related to this node in the underlying data:
113+
* ```ts
114+
* class Book extends TermWrapper {
115+
* set author(value: string) {
116+
* const subject = this as Quad_Subject
117+
* const predicate = this.factory.namedNode("http://example.com/author")
118+
* const object = this.factory.literal(value)
119+
* const oldAuthors = this.factory.quad(subject, predicate)
120+
* const newAuthor = this.factory.quad(subject, predicate, object)
121+
*
122+
* this.dataset.delete(oldAuthors)
123+
* this.dataset.add(newAuthor)
124+
* }
125+
* }
126+
* ```
127+
* Note: The above example operates on a low level to explain this property. Library users are more likely to interact with {@link OptionalAs}, {@link RequiredAs} and {@link LiteralFrom} for a better experience.
128+
*
129+
* @exmaple
130+
* Using the dataset to modify data related to this node in the underlying data:
131+
* ```ts
132+
* class Container extends TermWrapper {
133+
* add(something: string) {
134+
* const subject = this as Quad_Subject
135+
* const predicate = this.factory.namedNode("http://example.com/contains")
136+
* const object = this.factory.literal(something)
137+
* const quad = this.factory.quad(subject, predicate, object)
138+
*
139+
* this.dataset.add(quad)
140+
* }
141+
* }
142+
* ```
143+
*/
144+
get dataset(): DatasetCore {
145+
return this._dataset
146+
}
147+
148+
/**
149+
* The data factory this instance was instantiated with. A collection of methods that can be used to create terms by this or subsequent wrappers.
150+
*
151+
* @exmaple
152+
* Using the factory to create a literal term from the current date and time:
153+
* ```ts
154+
* class Calendar extends TermWrapper {
155+
* get currentDate(): Literal {
156+
* const date = new Date().toISOString()
157+
* const xsdDateTime = this.factory.namedNode("http://www.w3.org/2001/XMLSchema#dateTime")
158+
*
159+
* return this.factory.literal(date, xsdDateTime)
160+
* }
161+
* }
162+
* ```
163+
*
164+
* @exmaple
165+
* Using the factory to create a quad:
166+
* ```ts
167+
* class Container extends TermWrapper {
168+
* add(something: string) {
169+
* const subject = this as Quad_Subject
170+
* const predicate = this.factory.namedNode("http://example.com/contains")
171+
* const object = this.factory.literal(something)
172+
* const quad = this.factory.quad(subject, predicate, object)
173+
*
174+
* this.dataset.add(quad)
175+
* }
176+
* }
177+
* ```
178+
*/
179+
get factory(): DataFactory {
180+
return this._factory
181+
}
182+
183+
/**
184+
* The well-known property containing a string that represents the type of this object.
185+
*/
13186
get [Symbol.toStringTag]() {
14187
return this.constructor.name
15188
}
16189

190+
//#region Implementation of RDF/JS Term
191+
17192
get termType(): Term["termType"] {
18193
return this.original.termType
19194
}
@@ -22,6 +197,12 @@ export class TermWrapper implements IAnyTerm {
22197
return this.original.value
23198
}
24199

200+
equals(other: Term | null | undefined): boolean {
201+
return this.original.equals(other)
202+
}
203+
204+
//#region Implementation of RDF/JS Literal
205+
25206
get language(): string {
26207
return (this.original as Literal).language
27208
}
@@ -34,6 +215,10 @@ export class TermWrapper implements IAnyTerm {
34215
return (this.original as Literal).datatype
35216
}
36217

218+
//#endregion
219+
220+
//#region Implementation of RDF/JS Quad
221+
37222
get subject(): Term {
38223
return (this.original as BaseQuad).subject
39224
}
@@ -50,7 +235,7 @@ export class TermWrapper implements IAnyTerm {
50235
return (this.original as BaseQuad).graph
51236
}
52237

53-
equals(other: Term | null | undefined): boolean {
54-
return this.original.equals(other)
55-
}
238+
//#endregion
239+
240+
//#endregion
56241
}

src/ensure.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Literal, Term } from "@rdfjs/types"
22
import { TermTypeError } from "./errors/TermTypeError.js"
33
import { LiteralDatatypeError } from "./errors/LiteralDatatypeError.js"
4-
import type { IAnyTerm } from "./type/IAnyTerm.js"
4+
import type { IRdfJsTerm } from "./type/IRdfJsTerm.js"
55
import { RDF } from "./vocabulary/RDF.js"
66
import { ListRootError } from "./errors/ListRootError.js"
77

@@ -21,23 +21,23 @@ export function ensureIs(object: any, type: Function | { [Symbol.hasInstance]():
2121
throw new TypeError(`Object must be a ${type}`)
2222
}
2323

24-
export function ensureTermType(term: IAnyTerm, type: Term["termType"]) {
24+
export function ensureTermType(term: IRdfJsTerm, type: Term["termType"]) {
2525
if (term.termType === type) {
2626
return
2727
}
2828

2929
throw new TermTypeError(term as Term, type)
3030
}
3131

32-
export function ensureDatatype(term: IAnyTerm, ...datatypes: string[]) {
32+
export function ensureDatatype(term: IRdfJsTerm, ...datatypes: string[]) {
3333
if (datatypes.includes(term.datatype.value)) {
3434
return
3535
}
3636

3737
throw new LiteralDatatypeError(term as Literal, datatypes)
3838
}
3939

40-
export function ensureListRoot(term: IAnyTerm) {
40+
export function ensureListRoot(term: IRdfJsTerm) {
4141
if (term.termType === "NamedNode" && term.value === RDF.nil) {
4242
return
4343
}

src/errors/WrapperError.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,28 @@ export class WrapperError extends Error {
1313
this.name = this.constructor.name
1414
this.cause = cause
1515
}
16+
17+
//#region Ignore in documentation
18+
19+
/** @ignore */
20+
static override captureStackTrace(targetObject: object, constructorOpt?: Function) {
21+
super.captureStackTrace(targetObject, constructorOpt)
22+
}
23+
24+
/** @ignore */
25+
static override prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]) {
26+
super.prepareStackTrace(err, stackTraces)
27+
}
28+
29+
/** @ignore */
30+
static override get stackTraceLimit() {
31+
return super.stackTraceLimit
32+
}
33+
34+
/** @ignore */
35+
static override set stackTraceLimit(value) {
36+
super.stackTraceLimit = value
37+
}
38+
39+
//#endregion
1640
}

src/mapping/TermFrom.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { DataFactory, Term } from "@rdfjs/types"
2-
import type { IAnyTerm } from "../type/IAnyTerm.js"
2+
import type { IRdfJsTerm } from "../type/IRdfJsTerm.js"
33

44
/**
55
* A collection of {@link ITermAsValueMapping | mappers} that create RDF/JS terms from JavaScript primitives.
@@ -9,7 +9,7 @@ import type { IAnyTerm } from "../type/IAnyTerm.js"
99
* - [Nodes in RDF 1.1 Concepts and Abstract Syntax](https://www.w3.org/TR/rdf11-concepts/#dfn-node)
1010
*/
1111
export namespace TermFrom {
12-
export function instance(value: IAnyTerm, factory: DataFactory): Term {
12+
export function instance(value: IRdfJsTerm, factory: DataFactory): Term {
1313
return itself(value as Term, factory)
1414
}
1515

src/mod.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ export type * from "./type/ITermAsValueMapping.js"
22
export type * from "./type/ITermWrapperConstructor.js"
33
export type * from "./type/ITermFromValueMapping.js"
44
export type * from "./type/ILangString.js"
5-
export type * from "./type/IAnyTerm.js"
65

76
export * from "./decorators/GetterArity.js"
87
export * from "./decorators/SetterArity.js"

src/type/IAnyTerm.ts

Lines changed: 0 additions & 15 deletions
This file was deleted.

0 commit comments

Comments
 (0)