UNPKG

lincd

Version:

LINCD is a JavaScript library for building user interfaces with linked data (also known as 'structured data', or RDF)

385 lines 18 kB
/* * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ import { defaultGraph, Literal, NamedNode, Quad } from '../models.js'; import { getAndClearCallbacks, getNodeShapeUri, LINCD_DATA_ROOT, NodeShape, PropertyShape, ValidationReport, ValidationResult, } from '../shapes/SHACL.js'; import { Shape } from '../shapes/Shape.js'; import { Prefix } from './Prefix.js'; import { CoreSet } from '../collections/CoreSet.js'; import { lincd as lincdOntology } from '../ontologies/lincd.js'; import { npm } from '../ontologies/npm.js'; import { rdf } from '../ontologies/rdf.js'; import { URI } from './URI.js'; import { addNodeShapeToShapeClass, getShapeClass } from './ShapeClass.js'; import { createLinkedComponentFn, createLinkedSetComponentFn, } from '../utils/LinkedComponent.js'; import { shacl } from '../ontologies/shacl.js'; import { rdfs } from '../ontologies/rdfs.js'; import { xsd } from '../ontologies/xsd.js'; import { createPropertyShape } from '../shapes/SHACL.js'; // var packageParsePromises: Map<string,Promise<any>> = new Map(); // var loadedPackages: Set<NamedNode> = new Set(); let shapeToComponents = new Map(); let ontologies = new Set(); let _autoLoadOntologyData = false; /** * Convert some node to a prefixed format: * - http://some-example.org/prop > ex:prop * */ const prefix = (n) => Prefix.toPrefixed(n.uri); export var DEFAULT_LIMIT = 12; export function setDefaultPageLimit(limit) { DEFAULT_LIMIT = limit; } export function autoLoadOntologyData(value) { _autoLoadOntologyData = value; //this may be set to true after some ontologies have already indexed, if (_autoLoadOntologyData) { // so in that case we load all data of ontologies that are already indexed ontologies.forEach((ontologyExport) => { //see linkedOntology() where we store the data loading method under the _load key if (ontologyExport['_load']) { ontologyExport['_load'](); } }); } } export function linkedPackage(packageName) { let packageNode = NamedNode.getOrCreate(`${LINCD_DATA_ROOT}module/${packageName}`, true); let packageNameURI = URI.sanitize(packageName); //set certain values but don't emit change events or alteration events new Quad(packageNode, rdf.type, lincdOntology.Module, defaultGraph, false, false, false); new Quad(packageNode, npm.packageName, new Literal(packageName), defaultGraph, false, false, false); let packageTreeObject = registerPackageInTree(packageName); //#Create declarators for this module let registerPackageExport = function (object) { if (object.name in packageTreeObject) { console.warn(`Key ${object.name} was already defined for package ${packageName}. Note that LINCD currently only supports unique names across your entire package. Overwriting ${object.name} with new value`); } packageTreeObject[object.name] = object; }; let registerInPackageTree = function (exportName, exportedObject) { packageTreeObject[exportName] = exportedObject; }; function registerPackageModule(_module) { for (var key in _module.exports) { //if the exported object itself (usually FunctionalComponents) is not named or its name is _wrappedComponent (which ends up happening in the linkedComponent method above) //then we give it the same name as it's export name. if (!_module.exports[key].name || _module.exports[key].name === '_wrappedComponent') { Object.defineProperty(_module.exports[key], 'name', { value: key }); //manual 'hack' to set the name of the original function if (_module.exports[key]['original'] && !_module.exports[key]['original']['name']) { Object.defineProperty(_module.exports[key]['original'], 'name', { value: key + '_implementation', }); } } registerInPackageTree(key, _module.exports[key]); } } //create a declarator function which Components of this module can use register themselves and add themselves to the global tree let linkedUtil = function (constructor) { //add the component class of this module to the global tree registerPackageExport(constructor); //return the original class without modifications return constructor; }; //method to create a linked functional component const linkedComponent = createLinkedComponentFn(registerPackageExport, registerComponent); const linkedSetComponent = createLinkedSetComponentFn(registerPackageExport, registerComponent); // helper that contains the previous body; applies the decorator work to a given constructor function applyLinkedShape(constructor, options) { if (!constructor) { throw new Error('Constructor is undefined, skipping registration: ' + (constructor === null || constructor === void 0 ? void 0 : constructor.toString().substring(0, 100)) + ' ' + JSON.stringify(options)); return; } // add the component class of this module to the global tree registerPackageExport(constructor); // register the component and its shape Shape.registerByType(constructor); // if no shape object has been attached to the constructor if (!Object.getOwnPropertyNames(constructor).includes('shape')) { // create a new node shape for this shapeClass let nodeShape = NodeShape.getFromURI(getNodeShapeUri(packageName, constructor.name)); //small fix, for some reason sometimes a node with this URI already exists but is not a NodeShape if (!nodeShape.type) { nodeShape.type = shacl.NodeShape; } // connect the typescript class to its NodeShape constructor.shape = nodeShape; // set the name nodeShape.label = constructor.name; if (options) { if (options.description) { nodeShape.description = options.description; } } // also keep track of the reverse: nodeShape to typescript class addNodeShapeToShapeClass(nodeShape, constructor); // also create a representation in the graph of the shape class itself let shapeClass = NamedNode.getOrCreate(getNodeShapeUri(packageName, constructor.name), true); shapeClass.set(lincdOntology.definesShape, nodeShape.node); shapeClass.set(rdf.type, lincdOntology.ShapeClass); // and connect it back to the module shapeClass.set(lincdOntology.module, packageNode); //track what extends what (both on nodeShape level and shapeClass level) const extendingShapeClass = Object.getPrototypeOf(constructor); const extendingShape = extendingShapeClass.shape; //if this shape class is extending something other then Shape if (extendingShape && !(extendingShapeClass === Shape)) { //store which nodeShape this nodeShape extends nodeShape.extends = extendingShape; //store which shapeClass this shapeClass extends const extendingShapeClassNode = extendingShape.getOneInverse(lincdOntology.definesShape); if (extendingShapeClassNode) { shapeClass.set(lincdOntology.isExtending, extendingShapeClassNode); } } // run deferred callbacks from property decorators if (constructor['shapeCallbacks']) { constructor['shapeCallbacks'].forEach((callback) => { callback(nodeShape); }); const nodeCallbacks = getAndClearCallbacks(nodeShape.namedNode); if (nodeCallbacks) { nodeCallbacks.forEach((callback) => { callback(nodeShape); }); } delete constructor['shapeCallbacks']; } } else { console.warn('This ShapeClass already has a shape: ', constructor.shape); } if (constructor.targetClass) { constructor.shape.targetClass = constructor.targetClass; } // return the original class without modifications // return constructor; } function linkedShape(arg) { // usage as @linkedShape if (typeof arg === 'function') { applyLinkedShape(arg); return; } // usage as @linkedShape({...}) or @linkedShape() const options = arg; return function (constructor) { applyLinkedShape(constructor, options); }; } /** * * @param exports all exports of the file, simply provide "this" as value! * @param dataSource the path leading to the ontology's data file * @param nameSpace the base URI of the ontology * @param prefixAndFileName the file name MUST match the prefix for this ontology */ let linkedOntology = function (exports, nameSpace, prefixAndFileName, loadData, dataSource) { let exportsCopy = Object.assign({}, exports); //store specifics in exports. And make sure we can detect this as an ontology later exportsCopy['_ns'] = nameSpace; exportsCopy['_prefix'] = prefixAndFileName; exportsCopy['_load'] = loadData; exportsCopy['_data'] = dataSource; //register the prefix here (so just calling linkedOntology with a prefix will automatically register that prefix) if (prefixAndFileName) { //run the namespace without any term name, this will give back a named node with just the namespace as URI, then get that URI to provide it as full URI Prefix.add(prefixAndFileName, nameSpace('').uri); } ontologies.add(exportsCopy); //register all the exports under the prefix. NOTE: this means the file name HAS to match the prefix registerInPackageTree(prefixAndFileName, exportsCopy); // }); if (_autoLoadOntologyData) { loadData().catch((err) => { console.warn('Could not load ontology data. Do you need to rebuild the module of the ' + prefixAndFileName + ' ontology?', err); }); } }; /** * This method is used to get a shape class in this package by its name. * This can be used to avoid circular dependencies between shapes. * @param name */ let getPackageShape = (name) => { //get the named node of the node shape first, //then get the shape class that defines this node shape return getShapeClass(NamedNode.getOrCreate(getNodeShapeUri(packageName, name))); }; //return the declarators so the module can use them return { linkedComponent, linkedSetComponent, linkedShape, linkedUtil, linkedOntology, registerPackageExport, registerPackageModule, getPackageShape, packageExports: packageTreeObject, packageName: packageName, }; } function registerComponent(exportedComponent, shape) { if (!shape) { //warn developers against a common mistake: if no static shape is set by the Component it will inherit the one of the class it extends if (!exportedComponent.hasOwnProperty('shape')) { console.warn(`Component ${exportedComponent.displayName || exportedComponent.name} is not linked to a shape.`); return; } shape = exportedComponent.shape; } if (!shapeToComponents.has(shape)) { shapeToComponents.set(shape, new CoreSet()); } shapeToComponents.get(shape).add(exportedComponent); } function registerPackageInTree(packageName, packageExports) { //prepare name for global tree reference // let packageTreeKey = packageName.replace(/-/g,'_'); //if something with this name already registered in the global tree if (packageName in lincd._modules) { //This probably means package.ts is loaded twice, through different paths and could point to a problem //So we log about it. But there is one exception. LINCD itself registers itself twice: once in the bottom of this file and once in its package.ts file. //But if there are already other packages registered, then probably there is 2 versions of LINCD being loaded, and that IS a problem. if (packageName !== 'lincd' || Object.keys(lincd._modules).length !== 1) { console.warn('A package with the name ' + packageName + ' has already been registered. Adding to existing object'); } Object.assign(lincd._modules[packageName], packageExports); } else { //initiate an empty object for this module in the global tree lincd._modules[packageName] = packageExports || {}; } return lincd._modules[packageName]; } export function initTree() { let globalObject = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : undefined; if ('lincd' in globalObject) { throw new Error('Multiple versions of LINCD are loaded'); } else { globalObject['lincd'] = { _modules: {} }; } } //when this file is used, make sure the tree is initialized initTree(); //now that this file is set up, we can link linked shapes in the LINCD module itself let lincdPackage = linkedPackage('lincd'); lincdPackage.linkedShape({ description: 'Represents a SHACL NodeShape; defines constraints for a class of RDF nodes. Links to multiple PropertyShapes. (schema, constraint, class validation)', })(NodeShape); lincdPackage.linkedShape({ description: 'Represents a SHACL PropertyShape; specifies rules for one property of a NodeShape (path, datatype, cardinality). (validation rule, property constraint)', })(PropertyShape); lincdPackage.linkedShape({ description: 'ValidationReport produced by a SHACL engine; summarizes results of validating data against NodeShapes and PropertyShapes. (report, conformance, summary)', })(ValidationReport); lincdPackage.linkedShape({ description: 'Individual result entry in a ValidationReport; details a specific violation or success, pointing to the node, property, and constraint. (error, issue, finding)', })(ValidationResult); //ALL the following is to support Shape having get/set methods with property shapes //and Shape itself having a nodeShape //if we dont need Shape to have get/set methods (like label and type) then this can be removed Shape.shape = NodeShape.getFromURI('https://data.lincd.org/module/lincd/shape/shape'); addNodeShapeToShapeClass(Shape.shape, Shape); //Here we can register the properties of the Shape class itself //We can't do that inside of Shape because it would cause circular dependencies createPropertyShape({ path: rdfs.label, //TODO: multiple labels should be possible maxCount: 1, //currently get label is implemented to return a single value }, 'label', shacl.Literal, Shape); createPropertyShape({ path: rdf.type, shape: Shape, }, 'type', shacl.IRI, Shape); createPropertyShape({ path: shacl.property, shape: PropertyShape, }, 'properties', shacl.IRI, NodeShape); createPropertyShape({ path: rdfs.comment, maxCount: 1, }, 'description', shacl.Literal, NodeShape); createPropertyShape({ path: rdf.type, maxCount: 1, shape: Shape, }, 'type', shacl.IRI, NodeShape); createPropertyShape({ path: shacl.targetClass, shape: Shape, //should be rdfs Class, but that's currently not available in LINCD. So queries currently cannot continue after accessing targetClass maxCount: 1, }, 'targetClass', shacl.IRI, NodeShape); createPropertyShape({ path: shacl.description, maxCount: 1, }, 'type', shacl.Literal, NodeShape); createPropertyShape({ path: shacl.targetNode, shape: Shape, //actually returns a NamedNode... is this correct then? Should we define or use a rdfs Class that matches the potential values? }, 'targetNode', shacl.IRI, NodeShape); createPropertyShape({ path: lincdOntology.isExtending, shape: NodeShape, }, 'extends', shacl.IRI, NodeShape); //currently path accepts multiple values, so its a multi-value property //these values will be consequent properties that follow each other. Other property paths are not supported yet. createPropertyShape({ path: shacl.path, shape: Shape, }, 'path', shacl.IRI, PropertyShape); createPropertyShape({ path: shacl.node, shape: NodeShape, maxCount: 1, }, 'valueShape', shacl.IRI, PropertyShape); createPropertyShape({ maxCount: 1, path: shacl.nodeKind, shape: Shape, //actually returns a NamedNode. Queries currently cannot continue after accessing nodeKind }, 'nodeKind', shacl.IRI, PropertyShape); createPropertyShape({ path: shacl.datatype, shape: Shape, maxCount: 1, }, 'datatype', shacl.IRI, PropertyShape); //PropertyShape.maxCount createPropertyShape({ path: shacl.maxCount, datatype: xsd.integer, maxCount: 1, }, 'maxCount', shacl.Literal, PropertyShape); //PropertyShape.minCount createPropertyShape({ path: shacl.minCount, datatype: xsd.integer, maxCount: 1, }, 'minCount', shacl.Literal, PropertyShape); //PropertyShape.name createPropertyShape({ path: shacl.name, maxCount: 1, }, 'name', shacl.Literal, PropertyShape); //PropertyShape.description createPropertyShape({ path: shacl.description, maxCount: 1, }, 'description', shacl.Literal, PropertyShape); //PropertyShape.inList //# sourceMappingURL=Package.js.map