diff --git a/src/TermWrapper.ts b/src/TermWrapper.ts
index ac95ff5..192274f 100644
--- a/src/TermWrapper.ts
+++ b/src/TermWrapper.ts
@@ -1,19 +1,194 @@
-import type { BaseQuad, DataFactory, DatasetCore, Literal, NamedNode, Term } from "@rdfjs/types"
-import type { IAnyTerm } from "./type/IAnyTerm.js"
+import type { BaseQuad, DataFactory, DatasetCore, Literal, NamedNode, Quad_Subject, Term } from "@rdfjs/types"
+import type { IRdfJsTerm } from "./type/IRdfJsTerm.js"
-export class TermWrapper implements IAnyTerm {
+/**
+ * `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.
+ *
+ * @remarks
+ * 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.
+ *
+ * @example Basic usage
+ * 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:
+ * ```ts
+ * class SomeClass extends TermWrapper {
+ * get someProperty(): string {
+ * return RequiredFrom.subjectPredicate(this, "http://example.com/someProperty", LiteralAs.string)
+ * }
+ *
+ * set someProperty(value: string) {
+ * RequiredAs.object(this, "http://example.com/someProperty", value, LiteralFrom.string)
+ * }
+ * }
+ * ```
+ *
+ * Assume the following RDF data:
+ * ```turtle
+ * BASE
+ *
+ * "some value" .
+ * ```
+ *
+ * We can work with this data in JavaScript and TypeScript as follows:
+ * ```ts
+ * const dataset: DatasetCore // which has the RDF above loaded
+ * const instance = new SomeClass("http://example.com/someSubject", dataset, DataFactory)
+ *
+ * const value = instance.someProperty // contains "some value"
+ *
+ * instance.someProperty = "some other value" // underlying RDF is now "some other value" .
+ * ```
+ *
+ * @example Using instances of TermWrapper as instances of RDF/JS Term
+ * 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:
+ * ```ts
+ * let instance: TermWrapper
+ *
+ * // Our instance cast as Term
+ * const term = instance as Term
+ * ```
+ *
+ * @example Using instances of TermWrapper to create quads
+ * Instances of this class can be used anywhere an RDF/JS Term can be used, which includes creating quads:
+ * ```ts
+ * let instance: TermWrapper
+ * let factory: DataFactory
+ * const predicate = factory.namedNode("http://example.com/p")
+ * const object = factory.literal("o")
+ *
+ * // Our instance used as subject when creating a quad
+ * factory.quad(instance as Quad_Subject, predicate, object)
+ * ```
+ *
+ * @example Using instances of TermWrapper to match graph patterns
+ * Instances of this class can be used anywhere an RDF/JS Term can be used, which includes matching quads in a dataset:
+ * ```ts
+ * let instance: TermWrapper
+ * let dataset: DatasetCore
+ *
+ * // Our instance used as subject when matching statements in a dataset
+ * dataset.match(instance as Term)
+ * ```
+ */
+export class TermWrapper implements IRdfJsTerm {
private readonly original: Term
+ private readonly _dataset: DatasetCore
+ private readonly _factory: DataFactory
- public constructor(term: string, dataset: DatasetCore, factory: DataFactory)
- public constructor(term: Term, dataset: DatasetCore, factory: DataFactory)
- public constructor(term: string | Term, public readonly dataset: DatasetCore, public readonly factory: DataFactory) {
+ /**
+ * Creates a new instance of {@link TermWrapper}.
+ *
+ * @param term The IRI of a named node that is the original term being wrapped.
+ * @param dataset The dataset that contains the term being wrapped.
+ * @param factory A collection of methods for creating terms.
+ */
+ constructor(term: string, dataset: DatasetCore, factory: DataFactory)
+
+ /**
+ * Creates a new instance of {@link TermWrapper}.
+ *
+ * @param term The original term being wrapped.
+ * @param dataset The dataset that contains the term being wrapped.
+ * @param factory A collection of methods for creating terms.
+ */
+ 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._dataset = dataset
+ this._factory = factory
}
+ /**
+ * The dataset that contains this term.
+ *
+ * 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.
+ *
+ * @remarks
+ * 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.
+ *
+ * This property solves the problem by keeping a reference to the dataset.
+ *
+ * @exmaple
+ * Using the dataset to modify information related to this node in the underlying data:
+ * ```ts
+ * class Book extends TermWrapper {
+ * set author(value: string) {
+ * const subject = this as Quad_Subject
+ * const predicate = this.factory.namedNode("http://example.com/author")
+ * const object = this.factory.literal(value)
+ * const oldAuthors = this.factory.quad(subject, predicate)
+ * const newAuthor = this.factory.quad(subject, predicate, object)
+ *
+ * this.dataset.delete(oldAuthors)
+ * this.dataset.add(newAuthor)
+ * }
+ * }
+ * ```
+ * 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.
+ *
+ * @exmaple
+ * Using the dataset to modify data related to this node in the underlying data:
+ * ```ts
+ * class Container extends TermWrapper {
+ * add(something: string) {
+ * const subject = this as Quad_Subject
+ * const predicate = this.factory.namedNode("http://example.com/contains")
+ * const object = this.factory.literal(something)
+ * const quad = this.factory.quad(subject, predicate, object)
+ *
+ * this.dataset.add(quad)
+ * }
+ * }
+ * ```
+ */
+ get dataset(): DatasetCore {
+ return this._dataset
+ }
+
+ /**
+ * The data factory this instance was instantiated with. A collection of methods that can be used to create terms by this or subsequent wrappers.
+ *
+ * @exmaple
+ * Using the factory to create a literal term from the current date and time:
+ * ```ts
+ * class Calendar extends TermWrapper {
+ * get currentDate(): Literal {
+ * const date = new Date().toISOString()
+ * const xsdDateTime = this.factory.namedNode("http://www.w3.org/2001/XMLSchema#dateTime")
+ *
+ * return this.factory.literal(date, xsdDateTime)
+ * }
+ * }
+ * ```
+ *
+ * @exmaple
+ * Using the factory to create a quad:
+ * ```ts
+ * class Container extends TermWrapper {
+ * add(something: string) {
+ * const subject = this as Quad_Subject
+ * const predicate = this.factory.namedNode("http://example.com/contains")
+ * const object = this.factory.literal(something)
+ * const quad = this.factory.quad(subject, predicate, object)
+ *
+ * this.dataset.add(quad)
+ * }
+ * }
+ * ```
+ */
+ get factory(): DataFactory {
+ return this._factory
+ }
+
+ /**
+ * The well-known property containing a string that represents the type of this object.
+ */
get [Symbol.toStringTag]() {
return this.constructor.name
}
+ //#region Implementation of RDF/JS Term
+
get termType(): Term["termType"] {
return this.original.termType
}
@@ -22,6 +197,12 @@ export class TermWrapper implements IAnyTerm {
return this.original.value
}
+ equals(other: Term | null | undefined): boolean {
+ return this.original.equals(other)
+ }
+
+ //#region Implementation of RDF/JS Literal
+
get language(): string {
return (this.original as Literal).language
}
@@ -34,6 +215,10 @@ export class TermWrapper implements IAnyTerm {
return (this.original as Literal).datatype
}
+ //#endregion
+
+ //#region Implementation of RDF/JS Quad
+
get subject(): Term {
return (this.original as BaseQuad).subject
}
@@ -50,7 +235,7 @@ export class TermWrapper implements IAnyTerm {
return (this.original as BaseQuad).graph
}
- equals(other: Term | null | undefined): boolean {
- return this.original.equals(other)
- }
+ //#endregion
+
+ //#endregion
}
diff --git a/src/ensure.ts b/src/ensure.ts
index 31361b4..f2f05f5 100644
--- a/src/ensure.ts
+++ b/src/ensure.ts
@@ -1,7 +1,7 @@
import type { Literal, Term } from "@rdfjs/types"
import { TermTypeError } from "./errors/TermTypeError.js"
import { LiteralDatatypeError } from "./errors/LiteralDatatypeError.js"
-import type { IAnyTerm } from "./type/IAnyTerm.js"
+import type { IRdfJsTerm } from "./type/IRdfJsTerm.js"
import { RDF } from "./vocabulary/RDF.js"
import { ListRootError } from "./errors/ListRootError.js"
@@ -21,7 +21,7 @@ export function ensureIs(object: any, type: Function | { [Symbol.hasInstance]():
throw new TypeError(`Object must be a ${type}`)
}
-export function ensureTermType(term: IAnyTerm, type: Term["termType"]) {
+export function ensureTermType(term: IRdfJsTerm, type: Term["termType"]) {
if (term.termType === type) {
return
}
@@ -29,7 +29,7 @@ export function ensureTermType(term: IAnyTerm, type: Term["termType"]) {
throw new TermTypeError(term as Term, type)
}
-export function ensureDatatype(term: IAnyTerm, ...datatypes: string[]) {
+export function ensureDatatype(term: IRdfJsTerm, ...datatypes: string[]) {
if (datatypes.includes(term.datatype.value)) {
return
}
@@ -37,7 +37,7 @@ export function ensureDatatype(term: IAnyTerm, ...datatypes: string[]) {
throw new LiteralDatatypeError(term as Literal, datatypes)
}
-export function ensureListRoot(term: IAnyTerm) {
+export function ensureListRoot(term: IRdfJsTerm) {
if (term.termType === "NamedNode" && term.value === RDF.nil) {
return
}
diff --git a/src/errors/WrapperError.ts b/src/errors/WrapperError.ts
index b11e814..9c25599 100644
--- a/src/errors/WrapperError.ts
+++ b/src/errors/WrapperError.ts
@@ -13,4 +13,28 @@ export class WrapperError extends Error {
this.name = this.constructor.name
this.cause = cause
}
+
+ //#region Ignore in documentation
+
+ /** @ignore */
+ static override captureStackTrace(targetObject: object, constructorOpt?: Function) {
+ super.captureStackTrace(targetObject, constructorOpt)
+ }
+
+ /** @ignore */
+ static override prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]) {
+ super.prepareStackTrace(err, stackTraces)
+ }
+
+ /** @ignore */
+ static override get stackTraceLimit() {
+ return super.stackTraceLimit
+ }
+
+ /** @ignore */
+ static override set stackTraceLimit(value) {
+ super.stackTraceLimit = value
+ }
+
+ //#endregion
}
diff --git a/src/mapping/TermFrom.ts b/src/mapping/TermFrom.ts
index 8642cda..c091e15 100644
--- a/src/mapping/TermFrom.ts
+++ b/src/mapping/TermFrom.ts
@@ -1,5 +1,5 @@
import type { DataFactory, Term } from "@rdfjs/types"
-import type { IAnyTerm } from "../type/IAnyTerm.js"
+import type { IRdfJsTerm } from "../type/IRdfJsTerm.js"
/**
* 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"
* - [Nodes in RDF 1.1 Concepts and Abstract Syntax](https://www.w3.org/TR/rdf11-concepts/#dfn-node)
*/
export namespace TermFrom {
- export function instance(value: IAnyTerm, factory: DataFactory): Term {
+ export function instance(value: IRdfJsTerm, factory: DataFactory): Term {
return itself(value as Term, factory)
}
diff --git a/src/mod.ts b/src/mod.ts
index f94b515..c46150f 100644
--- a/src/mod.ts
+++ b/src/mod.ts
@@ -2,7 +2,6 @@ export type * from "./type/ITermAsValueMapping.js"
export type * from "./type/ITermWrapperConstructor.js"
export type * from "./type/ITermFromValueMapping.js"
export type * from "./type/ILangString.js"
-export type * from "./type/IAnyTerm.js"
export * from "./decorators/GetterArity.js"
export * from "./decorators/SetterArity.js"
diff --git a/src/type/IAnyTerm.ts b/src/type/IAnyTerm.ts
deleted file mode 100644
index 8d6f04c..0000000
--- a/src/type/IAnyTerm.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-import type { Literal, NamedNode, Term } from "@rdfjs/types"
-
-export interface IAnyTerm {
- readonly termType: Term["termType"]
- readonly value: string
- readonly language: string
- readonly direction: Literal["direction"]
- readonly datatype: NamedNode
- readonly subject: Term
- readonly predicate: Term
- readonly object: Term
- readonly graph: Term
-
- equals(other: Term | null | undefined): boolean
-}
diff --git a/src/type/IRdfJsTerm.ts b/src/type/IRdfJsTerm.ts
new file mode 100644
index 0000000..9429103
--- /dev/null
+++ b/src/type/IRdfJsTerm.ts
@@ -0,0 +1,63 @@
+import type { Literal, NamedNode, Quad, Term } from "@rdfjs/types"
+
+export interface IRdfJsTerm {
+ /**
+ * @see {@link Term.termType}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly termType: Term["termType"]
+
+ /**
+ * @see {@link Term.value}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly value: string
+
+ /**
+ * @see {@link Literal.language}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly language: string
+
+ /**
+ * @see {@link Literal.direction}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly direction: Literal["direction"]
+
+ /**
+ * @see {@link Literal.datatype}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly datatype: NamedNode
+
+ /**
+ * @see {@link Quad.subject}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly subject: Term
+
+ /**
+ * @see {@link Quad.predicate}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly predicate: Term
+
+ /**
+ * @see {@link Quad.object}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly object: Term
+
+ /**
+ * @see {@link Quad.graph}
+ * @group Implementation of RDF/JS Term
+ */
+ readonly graph: Term
+
+ /**
+ * @see {@link Term.equals}
+ * @group Implementation of RDF/JS Term
+ */
+ equals(other: Term | null | undefined): boolean
+}
diff --git a/typedoc.json b/typedoc.json
index e01bd9c..e5134b7 100644
--- a/typedoc.json
+++ b/typedoc.json
@@ -22,10 +22,19 @@
"DefaultGraph": "https://rdf.js.org/data-model-spec/#defaultgraph-interface",
"Literal": "https://rdf.js.org/data-model-spec/#literal-interface",
"Literal.datatype": "https://rdf.js.org/data-model-spec/#dom-literal-datatype",
+ "Literal.direction": "https://rdf.js.org/data-model-spec/#dom-literal-direction",
+ "Literal.language": "https://rdf.js.org/data-model-spec/#dom-literal-language",
"NamedNode": "https://rdf.js.org/data-model-spec/#namednode-interface",
"Quad": "https://rdf.js.org/data-model-spec/#quad-interface",
+ "Quad.graph": "https://rdf.js.org/data-model-spec/#dom-quad-graph",
+ "Quad.object": "https://rdf.js.org/data-model-spec/#dom-quad-object",
+ "Quad.predicate": "https://rdf.js.org/data-model-spec/#dom-quad-predicate",
+ "Quad.subject": "https://rdf.js.org/data-model-spec/#dom-quad-subject",
"Term": "https://rdf.js.org/data-model-spec/#term-interface",
- "Variable": "https://rdf.js.org/data-model-spec/#variable-interface"
+ "termType": "https://rdf.js.org/data-model-spec/#dom-term-termtype",
+ "Variable": "https://rdf.js.org/data-model-spec/#variable-interface",
+ "value": "https://rdf.js.org/data-model-spec/#dom-term-value",
+ "equals": "https://rdf.js.org/data-model-spec/#dom-term-equals"
}
}
}