@tpluscode/rdfine
Version:
RDF/JS idiomatic, native, effective
215 lines (214 loc) • 8.52 kB
JavaScript
import once from 'onetime';
import TypeCollectionCtor from './lib/TypeCollection.js';
import { toJSON } from './lib/toJSON.js';
import { fromInitializer } from './lib/resource.js';
import { mixins } from './lib/mixins.js';
export default class RdfResourceImpl {
static { this.__mixins = []; }
static { this.__properties = new Map(); }
static { this.__initializers = new Map(); }
static _userInitializeProperties(resource, init = {}) {
Object.entries(init)
.filter(([prop]) => prop !== 'id' && prop !== 'types')
.forEach(([prop, value]) => {
if (!prop.startsWith('http')) {
// use decorated setter property
if (typeof value === 'function') {
resource[prop] = value(resource.pointer.any());
}
else {
resource[prop] = value;
}
return;
}
const values = Array.isArray(value) ? value : [value];
const pointers = values.map(function toPointer(value) {
if (typeof value === 'function') {
const result = value(resource.pointer.any());
if (typeof result === 'function') {
throw new Error('Initializer factory function cannot return a function');
}
if (Array.isArray(result)) {
throw new Error('Initializer factory function cannot return an array');
}
return toPointer(result);
}
if (typeof value === 'object' && 'term' in value) {
return value.term;
}
if (typeof value === 'object' && 'pointer' in value) {
return value.pointer;
}
if (typeof value === 'object' && 'termType' in value) {
return resource.pointer.node(value);
}
let literal;
if (typeof value === 'object' && 'value' in value && 'datatype' in value) {
literal = resource.env.rdfine().convert.toLiteral(value.value, value.datatype);
}
else {
literal = resource.env.rdfine().convert.toLiteral(value);
}
if (literal) {
return resource.pointer.node(literal);
}
// create and initialize an object resource
const childResource = fromInitializer(resource, value);
return childResource.pointer;
});
resource.pointer.addOut(resource.pointer.namedNode(prop), pointers);
});
for (const type of init.types || []) {
resource.types.add(type);
}
}
constructor(pointer, parentOrEnv, init = {}) {
this.__initialized = false;
if (pointer.term.termType !== 'BlankNode' && pointer.term.termType !== 'NamedNode') {
throw new Error(`RdfResource cannot be initialized from a ${pointer.term.termType} node`);
}
let parent;
if ('id' in parentOrEnv) {
parent = parentOrEnv;
this.env = parent.env;
}
else {
this.env = parentOrEnv;
}
/* TODO: when clownface gets graph feature
if (pointer._context[0].graph) {
this.pointer = pointer
this.unionGraphPointer = pointer.fromGraph(null)
} else {
this.pointer = pointer.fromGraph(pointer.defaultGraph)
this.unionGraphPointer = pointer
} */
const selfGraph = this.env.clownface({
...pointer,
term: pointer.term,
});
if (selfGraph._context[0].graph) {
this.pointer = selfGraph;
this.unionGraphPointer = this.env.clownface({ dataset: selfGraph.dataset, term: selfGraph.term, graph: undefined });
}
else {
this.pointer = this.env.clownface({ dataset: selfGraph.dataset, term: selfGraph.term, graph: this.env.defaultGraph() });
this.unionGraphPointer = this.env.clownface({ dataset: selfGraph.dataset, term: selfGraph.term });
}
this.__initializeProperties = once(() => {
const self = this;
const defaults = [...mixins(self)].flatMap((mixinProto) => [...mixinProto.__initializers]);
defaults.forEach(([key, value]) => {
const currentValue = self[key];
const valueIsEmptyArray = Array.isArray(currentValue) && currentValue.length === 0;
const valueIsUndefined = typeof currentValue === 'undefined';
if (!(valueIsEmptyArray || valueIsUndefined)) {
return;
}
if (typeof value === 'function') {
self[key] = value(self);
return;
}
self[key] = value;
});
return true;
});
this._parent = parent;
this.__initialized = this.__initializeProperties();
RdfResourceImpl._userInitializeProperties(this, init);
}
get id() {
return this.pointer.term;
}
get _graphId() {
return this.pointer._context[0].graph;
}
get types() {
return new TypeCollectionCtor(this);
}
get isAnonymous() {
return this.id.termType === 'BlankNode';
}
hasType(type) {
return this.types.has(type);
}
equals(other) {
if (!other) {
return false;
}
if ('termType' in other) {
return this.id.equals(other);
}
const otherPointer = '_context' in other ? other : other.pointer;
const idsEqual = this.id.equals(otherPointer.term);
if (this.isAnonymous || otherPointer.term.termType === 'BlankNode') {
return idsEqual && this.pointer.dataset === otherPointer.dataset;
}
return idsEqual;
}
get(property, options) {
const objects = this.getArray(property, options);
if (objects.length > 0) {
return objects[0];
}
return null;
}
getArray(property, options) {
const values = this._getObjects(property, options)
.filter(obj => obj.term.termType === 'NamedNode' || obj.term.termType === 'BlankNode')
.map(obj => {
return this._create(obj, [], { parent: this });
});
if (!values.length) {
return [];
}
return values;
}
getNumber(property, options) {
const [value] = this._getObjects(property, options).toArray();
if (typeof value === 'undefined') {
return null;
}
if (value.term.termType === 'Literal') {
return parseFloat(value.value);
}
throw new Error(`Expected property '${property}' to be a number but found '${value}'`);
}
getString(property, options) {
const [value] = this._getObjects(property, options).toArray();
if (typeof value === 'undefined') {
return null;
}
if (value.term.termType === 'Literal') {
return value.value;
}
throw new Error(`Expected property '${property}' to be a literal but found '${value}'`);
}
getBoolean(property, options) {
const [value] = this._getObjects(property, options).toArray();
if (typeof value === 'undefined') {
return false;
}
if (value.term.termType === 'Literal' && this.env.ns.xsd.boolean.equals(value.term.datatype)) {
return value.term.equals(this.pointer.literal(true).term);
}
throw new Error(`Expected property '${property}' to be a boolean but found '${value}'`);
}
_getObjects(property, { strict } = { strict: false }) {
const propertyNode = typeof property === 'string' ? this.pointer.namedNode(property) : property;
const objects = this.pointer.out(propertyNode);
if (!objects.terms.length && strict) {
throw new Error(`Value for predicate <${property}> was missing`);
}
return objects;
}
_create(term, mixins, options = {}) {
return this.env.rdfine().factory.createEntity(term, mixins, options);
}
toJSON() {
return toJSON(this);
}
}
export function fromObject(initializer) {
return initializer;
}