Skip to content
Draft
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
76 changes: 76 additions & 0 deletions src/mapping/LiteralFrom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,39 @@ export namespace LiteralFrom {
return factory.literal((value as any).toBase64(), factory.namedNode(XSD.base64Binary))
}

/**
* {@link ITermFromValueMapping Maps} a big integer to an integer literal.
*
* @param value - The big integer to convert.
* @param factory - A collection of methods for creating terms.
* @returns A literal with datatype [`xsd:integer`](https://www.w3.org/TR/xmlschema-2/#integer) whose lexical form is the decimal representation of the value.
*
* @remarks
* This is the term counterpart of the {@link LiteralAs.bigint} value mapping, so integers too large for a JavaScript `number` round-trip through RDF without loss of precision.
*
* @example Assign a bigint beyond Number.MAX_SAFE_INTEGER
* The mapping
* ```ts
* class Class extends TermWrapper {
* public set property(value: bigint) {
* RequiredAs.object(this, "p", value, LiteralFrom.bigint)
* }
* }
* ```
*
* assigned the value `9007199254740993n` produces the RDF
* ```turtle
* <s> <p> "9007199254740993"^^<xsd:integer> .
* ```
*
* @see
* - [`xsd:integer` in XML Schema Part 2: Datatypes](https://www.w3.org/TR/xmlschema-2/#integer)
* - [`BigInt`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt)
*/
export function bigint(value: bigint, factory: DataFactory): Term {
return factory.literal(value.toString(), factory.namedNode(XSD.integer))
}

export function boolean(value: boolean, factory: DataFactory): Term {
return factory.literal(value.toString(), factory.namedNode(XSD.boolean))
}
Expand Down Expand Up @@ -57,6 +90,49 @@ export namespace LiteralFrom {
return factory.literal(value)
}

/**
* {@link ITermFromValueMapping Maps} a registered symbol to a string literal.
*
* @param value - The symbol to convert. Must be registered in the global symbol registry, i.e. created via [`Symbol.for()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for).
* @param factory - A collection of methods for creating terms.
* @returns A string literal whose value is the key under which the symbol is registered.
*
* @remarks
* This is the term counterpart of the {@link LiteralAs.symbol} value mapping, which converts literals to symbols via [`Symbol.for()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for).
*
* Only symbols in the global symbol registry carry a key ([`Symbol.keyFor()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/keyFor)) that can round-trip through RDF, so unregistered symbols (e.g. created via the `Symbol()` function) are rejected.
*
* @throws {@link !TypeError TypeError} If the symbol is not registered in the global symbol registry.
*
* @example Assign a registered symbol
* The mapping
* ```ts
* class Class extends TermWrapper {
* public set property(value: symbol) {
* RequiredAs.object(this, "p", value, LiteralFrom.symbol)
* }
* }
* ```
*
* assigned the value `Symbol.for("example")` produces the RDF
* ```turtle
* <s> <p> "example" .
* ```
*
* @see
* - [`Symbol.for()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/for)
* - [`Symbol.keyFor()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/keyFor)
*/
export function symbol(value: symbol, factory: DataFactory): Term {
const key = Symbol.keyFor(value)

if (key === undefined) {
throw new TypeError("Symbol must be registered in the global symbol registry")
}

return factory.literal(key)
}

export function langTuple([key, value]: [string, string], factory: DataFactory): Term {
return factory.literal(value, key)
}
Expand Down
34 changes: 34 additions & 0 deletions test/unit/literalFrom.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { describe, it } from "node:test"
import { LiteralFrom } from "@rdfjs/wrapper"
import { DataFactory } from "n3"
import assert from "node:assert"
import { Parent } from "./model/Parent.js"
import { datasetFromRdf } from "./util/datasetFromRdf.js"

const XSD = "http://www.w3.org/2001/XMLSchema#"

Expand All @@ -21,4 +23,36 @@ await describe("LiteralFrom", async () => {
assert.strictEqual((term as any).datatype.value, `${XSD}dateTime`)
})
})

await describe("bigint", async () => {
await it("produces an xsd:integer lexical", async () => {
const term = LiteralFrom.bigint(9007199254740993n, DataFactory)
assert.strictEqual(term.value, "9007199254740993")
assert.strictEqual((term as any).datatype.value, `${XSD}integer`)
})

await it("round-trips through a model property without loss of precision", async () => {
const parent = new Parent("x", datasetFromRdf(""), DataFactory)
parent.hasBigint = 9007199254740993n
assert.strictEqual(parent.hasBigint, 9007199254740993n)
})
})

await describe("symbol", async () => {
await it("produces a string literal from a registered symbol", async () => {
const term = LiteralFrom.symbol(Symbol.for("example"), DataFactory)
assert.strictEqual(term.value, "example")
assert.strictEqual((term as any).datatype.value, `${XSD}string`)
})

await it("round-trips through a model property", async () => {
const parent = new Parent("x", datasetFromRdf(""), DataFactory)
parent.hasSymbol = Symbol.for("example")
assert.strictEqual(parent.hasSymbol, Symbol.for("example"))
})

await it("throws a TypeError for a symbol not in the global symbol registry", async () => {
assert.throws(() => LiteralFrom.symbol(Symbol("example"), DataFactory), TypeError)
})
})
})
16 changes: 16 additions & 0 deletions test/unit/model/Parent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ import { Example } from "../vocabulary/Example.js"

export class Parent extends TermWrapper {
/* Value Mapping */
public get hasBigint(): bigint {
return RequiredFrom.subjectPredicate(this, Example.hasBigint, LiteralAs.bigint)
}

public get hasBlankNode(): string {
return RequiredFrom.subjectPredicate(this, Example.hasBlankNode, LiteralAs.string)
}
Expand Down Expand Up @@ -46,8 +50,16 @@ export class Parent extends TermWrapper {
return RequiredFrom.subjectPredicate(this, Example.hasIri, LiteralAs.string)
}

public get hasSymbol(): symbol {
return RequiredFrom.subjectPredicate(this, Example.hasSymbol, LiteralAs.symbol)
}


/* Term Mapping */
public set hasBigint(value: bigint) {
RequiredAs.object(this, Example.hasBigint, value, LiteralFrom.bigint)
}

public set hasBlankNode(value: string) {
RequiredAs.object(this, Example.hasBlankNode, value, BlankNodeFrom.string)
}
Expand Down Expand Up @@ -76,6 +88,10 @@ export class Parent extends TermWrapper {
RequiredAs.object(this, Example.hasIri, value, NamedNodeFrom.string)
}

public set hasSymbol(value: symbol) {
RequiredAs.object(this, Example.hasSymbol, value, LiteralFrom.symbol)
}


/* Object Mapping */
public get hasChild(): Child {
Expand Down
2 changes: 2 additions & 0 deletions test/unit/vocabulary/Example.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
export const Example = {
/* Term and Value mapping */
hasBigint: "https://example.org/hasBigint",
hasBlankNode: "https://example.org/hasBlankNode",
hasDate: "https://example.org/hasDate",
hasLangString: "https://example.org/hasLangString",
hasNumber: "https://example.org/hasNumber",
hasBoolean: "https://example.org/hasBoolean",
hasIri: "https://example.org/hasIri",
hasString: "https://example.org/hasString",
hasSymbol: "https://example.org/hasSymbol",
/* Object mapping */
hasChild: "https://example.org/hasChild",
/* Set mapping */
Expand Down