UNPKG

@tpluscode/rdfine

Version:
209 lines (208 loc) 8.36 kB
import { toEdgeTraversals } from '../../path.js'; import { enumerateList, isList } from '../../rdf-list.js'; import { onlyUnique } from '../../filter.js'; function getObjects(env, subjects, path) { const nodes = path.reduce((subjects, edge) => { const objects = []; subjects.forEach(subject => { objects.push(...edge(subject, env)); }); return objects; }, subjects); return nodes.reduce((contexts, node) => { return contexts.concat(node.toArray()); }, []); } function getNodeFromEveryGraph(node, env) { const graphs = node.datasets.reduce((set, dataset) => { return [...dataset].reduce((set, quad) => { return set.add(quad.graph); }, set); }, env.termSet()); const graphNodes = [...graphs.values()]; if (!graphNodes.length) { return [node]; } // TODO: when clownface gets graph feature // return graphNodes.map(graph => node.fromGraph(graph)) return graphNodes.map(graph => env.clownface({ dataset: node.dataset, term: node.term, graph, })); } function createProperty(proto, name, options) { const { fromTerm, toTerm, assertSetValue, valueTypeName, initial, strict, compare, subjectFromAllGraphs, filter } = options; let values = ['single']; if (Array.isArray(options.values)) { values = options.values; } else if (options.values) { values = [options.values]; } const getPath = (resource) => Array.isArray(options.path) ? toEdgeTraversals(proto.constructor.__ns, resource.env, options.path) : toEdgeTraversals(proto.constructor.__ns, resource.env, [options.path || name]); Object.defineProperty(proto, name, { get() { const rootNode = subjectFromAllGraphs ? getNodeFromEveryGraph(this.pointer, this.env) : [this.pointer]; const path = getPath(this); let nodes = getObjects(this.env, rootNode, path); const crossesBoundaries = path.some(edge => edge.crossesGraphBoundaries); if (subjectFromAllGraphs || crossesBoundaries) { values = ['array']; } if (filter) { nodes = nodes.filter(node => filter(node.term)); } const returnValues = nodes.map((obj, index) => { if (isList(obj)) { if (index > 0) { throw new Error('Lists of lists are not supported'); } return enumerateList(this, obj, fromTerm.bind(this)); } return fromTerm.call(this, obj); }).filter(onlyUnique(compare)); if (values.includes('array') && returnValues.length !== 1) { return returnValues; } if (values.includes('list') && Array.isArray(returnValues[0])) { return returnValues[0] || []; } if (returnValues.length > 1 && !values.includes('array')) { throw new Error(`${name}: Multiple terms found where 0..1 was expected`); } if (Array.isArray(returnValues[0]) && !values.includes('list')) { throw new Error(`${name}: RDF List found where 0..1 object was expected`); } if (this.__initialized && strict && returnValues.length === 0) { throw new Error(`Object not found for property ${name}`); } return values.includes('single') ? returnValues[0] : returnValues; }, set(value) { if (!values.includes('array') && !values.includes('list') && Array.isArray(value)) { throw new Error(`${name}: Cannot set array to a non-array property`); } const path = getPath(this); const subjects = path.length === 1 ? this.pointer.toArray() : getObjects(this.env, [this.pointer], path.slice(0, path.length - 1)); if (subjects.length > 1) { throw new Error('Cannot set value to multiple nodes at once'); } const subject = subjects[0]; const lastPredicate = path[path.length - 1].predicate; subject.out(lastPredicate).forEach(obj => { if (isList(obj)) { subject.deleteList(lastPredicate); } }); subject.deleteOut(lastPredicate); if (value === null || typeof value === 'undefined') { return; } let initializedArray = false; let valueArray; if (Array.isArray(value)) { initializedArray = true; valueArray = value; } else { valueArray = [value]; } const termsArray = valueArray.reduce((terms, valueOrFactory) => { let term; let value; if (typeof valueOrFactory === 'function') { value = valueOrFactory(this.pointer.any()); } else { value = valueOrFactory; } if (value && typeof value === 'object' && 'termType' in value) { term = value; } else if (value && typeof value === 'object' && 'term' in value) { term = value.term; } else if (value && typeof value === 'object' && 'pointer' in value) { term = value.id; } else { term = toTerm.call(this, value); } if (filter && !filter(term)) { return terms; } if (!assertSetValue(value)) { const pathStr = path.map(edge => `<${edge.predicate.value}>`).join('/'); throw new Error(`Unexpected value for path ${pathStr}. Expecting a ${valueTypeName} or RDF/JS term.`); } return [...terms, term]; }, []); if (values.includes('list') && (values.length === 1 || initializedArray)) { // only set RDF List when property only allows lists or explicitly initialized with array if (termsArray.length === 0) { subject.addOut(lastPredicate, this.env.ns.rdf.nil); } else { subject.addList(lastPredicate, termsArray); } } else { subject.addOut(lastPredicate, termsArray); } }, }); if (!Object.hasOwnProperty.call(proto.constructor, '__properties')) { proto.constructor.__properties = new Map(); } if (!Object.hasOwnProperty.call(proto.constructor, '__initializers')) { proto.constructor.__initializers = new Map(); } proto.constructor.__properties.set(name, { initial, options: { ...options, values, }, }); if (initial) { proto.constructor.__initializers.set(name, initial); } } const legacyProperty = (options, proto, name) => { createProperty(proto, name.toString(), options); }; const standardProperty = (options, element) => { return { kind: 'field', key: Symbol(), placement: 'own', descriptor: {}, // When @babel/plugin-proposal-decorators implements initializers, // do this instead of the initializer below. See: // https://github.com/babel/babel/issues/9260 extras: [ // { // kind: 'initializer', // placement: 'own', // initializer: descriptor.initializer, // } // ], initializer() { if (typeof element.initializer === 'function') { this[element.key] = element.initializer.call(this); } }, finisher(clazz) { createProperty(clazz.prototype, element.key.toString(), options); }, }; }; export function propertyDecorator(options) { return (protoOrDescriptor, name) => (name !== undefined) ? legacyProperty(options, protoOrDescriptor, name) : standardProperty(options, protoOrDescriptor); }