UNPKG

lincd

Version:

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

869 lines 53.9 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.TestNode = exports.LINCD_DATA_ROOT = void 0; exports.autoLoadOntologyData = autoLoadOntologyData; exports.linkedPackage = linkedPackage; exports.initTree = initTree; /* * 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/. */ const models_1 = require("../models"); const Shape_1 = require("../shapes/Shape"); const SHACL_1 = require("../shapes/SHACL"); const Prefix_1 = require("./Prefix"); const CoreSet_1 = require("../collections/CoreSet"); const rdf_1 = require("../ontologies/rdf"); const lincd_1 = require("../ontologies/lincd"); const npm_1 = require("../ontologies/npm"); const react_1 = __importStar(require("react")); const rdfs_1 = require("../ontologies/rdfs"); const NodeSet_1 = require("../collections/NodeSet"); const LinkedStorage_1 = require("./LinkedStorage"); const ShapeSet_1 = require("../collections/ShapeSet"); const URI_1 = require("./URI"); const ShapeClass_1 = require("./ShapeClass"); exports.LINCD_DATA_ROOT = 'https://data.lincd.org/'; // 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; 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'](); } }); } } function linkedPackage(packageName) { let packageNode = models_1.NamedNode.getOrCreate(`${exports.LINCD_DATA_ROOT}module/${packageName}`, true); //set certain values but don't emit change events or alteration events new models_1.Quad(packageNode, rdf_1.rdf.type, lincd_1.lincd.Module, models_1.defaultGraph, false, false, false); new models_1.Quad(packageNode, npm_1.npm.packageName, new models_1.Literal(packageName), models_1.defaultGraph, false, false, false); // packageNode.set(rdf.type, lincdOntology.Module); // packageNode.setValue(npm.packageName, packageName); 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 function linkedComponent(requiredData, functionalComponent) { let [shapeClass, dataRequest, dataDeclaration, tracedDataResponse] = processDataDeclaration(requiredData, functionalComponent); //create a new functional component which wraps the original //also, first of all use React.forwardRef to support OPTIONAL use of forwardRef by the linked component itself //Combining HOC (Linked Component) with forwardRef was tricky to understand and get to work. Inspiration came from: https://dev.to/justincy/using-react-forwardref-and-an-hoc-on-the-same-component-455m let _wrappedComponent = react_1.default.forwardRef((props, ref) => { //take the given props and add make sure 'of' is converted to 'source' (an instance of the shape) let linkedProps = getLinkedComponentProps(props, shapeClass); //if a ref was given, we need to manually add it back to the props, React will extract it and provide is as second argument to React.forwardRef in the linked component itself if (ref) { linkedProps['ref'] = ref; } if (!linkedProps.source) { console.warn('No source provided to this component: ' + functionalComponent.name); return null; } //if we're not using any storage in this LINCD app, don't do any data loading let usingStorage = LinkedStorage_1.LinkedStorage.isInitialised(); let [isLoaded, setIsLoaded] = (0, react_1.useState)(undefined); (0, react_1.useEffect)(() => { //if this property is not bound (if this component is bound we can expect all properties to be loaded by the time it renders) if (!props.isBound && usingStorage) { let cachedRequest = LinkedStorage_1.LinkedStorage.isLoaded(linkedProps.source.node, dataRequest); //if these properties were requested before and have finished loading if (cachedRequest === true) { //then we can set state to loaded straight away setIsLoaded(true); } else if (cachedRequest === false) { //if we did not request all these properties before then we continue to // load the required PropertyShapes from storage for this specific source LinkedStorage_1.LinkedStorage.loadShape(linkedProps.source, dataRequest).then((quads) => { //set the 'isLoaded' state to true, so we don't need to even check cache again. setIsLoaded(true); }); } else { //if some requiredProperties are still being loaded //cachedResult will be a promise (there is no other return type) //(this may happen when a different component already requested the same properties for the same source just before this sibling component) //wait for that loading to be completed and then update the state cachedRequest.then(() => { setIsLoaded(true); }); } } }, [linkedProps.source.node, props.isBound]); //we can assume data is loaded if this is a bound component or if the isLoaded state has been set to true let dataIsLoaded = props.isBound || isLoaded || !usingStorage; //But for the first render, when the useEffect has not run yet, //and no this is not a bound component (so it's a top level linkedComponent), //then we still need to manually check cache to avoid a rendering a temporary load icon until useEffect has run (in the case the data is already loaded) if (!props.isBound && typeof isLoaded === 'undefined' && usingStorage) { //only continue to render if the result is true (all required data loaded), // if it's a promise we already deal with that in useEffect() dataIsLoaded = LinkedStorage_1.LinkedStorage.isLoaded(linkedProps.source.node, dataRequest) === true; } //if the data is loaded //TODO: remove check for typeof window, this is temporary solution to fix hydration errors // but really we should find a way to send the data to the frontend for initial page loads AND notify storage that that data is loaded // then this check can be turned off. We can possibly do this with RDFA (rdf in html), then we can probably parse the data from the html, whilst rendering it on the server in one go. if (dataIsLoaded && typeof window !== 'undefined') { //if the component used a Shape.requestSet() data declaration function if (dataDeclaration) { //then use that now to get the requested linkedData for this instance linkedProps.linkedData = getLinkedDataResponse(dataDeclaration.request, linkedProps.source, tracedDataResponse); } // //render the original components with the original + generated properties return react_1.default.createElement(functionalComponent, linkedProps); } else { //render loading return (0, react_1.createElement)('div', null, '...'); } }); //connect the Component.of() function, which is called bindComponentToData here //<DeclaredProps & LinkedComponentInputProps<ShapeType>, ShapeType> _wrappedComponent.of = bindComponentToData.bind(_wrappedComponent, tracedDataResponse); //keep a copy of the original for strict checking of equality when compared to _wrappedComponent.original = functionalComponent; _wrappedComponent.dataRequest = dataRequest; //link the wrapped functional component to its shape _wrappedComponent.shape = shapeClass; //IF this component is a function that has a name if (functionalComponent.name) { //then copy the name (have to do it this way, name is protected) Object.defineProperty(_wrappedComponent, 'name', { value: functionalComponent.name, }); //and add the component class of this module to the global tree registerPackageExport(_wrappedComponent); } //NOTE: if it does NOT have a name, the developer will need to manually use registerPackageExport //register the component and its shape registerComponent(_wrappedComponent, shapeClass); return _wrappedComponent; } function linkedSetComponent(requiredData, functionalComponent) { let [shapeClass, dataRequest, dataDeclaration, tracedDataResponse] = processDataDeclaration(requiredData, functionalComponent, true); //if we're not using any storage in this LINCD app, don't do any data loading let usingStorage = LinkedStorage_1.LinkedStorage.isInitialised(); //create a new functional component which wraps the original let _wrappedComponent = (props) => { //take the given props and add make sure 'of' is converted to 'source' (an instance of the shape) let linkedProps = getLinkedSetComponentProps(props, shapeClass, functionalComponent); //if this component was created with an 'as' attribute (so <SetComponent of={sources} as={ChildComponent} /> if (props.as) { //then we should make that available as the ChildComponent (in other cases ChildComponent is already defined by children or dataRequest, but here we need to do it based on props) linkedProps.ChildComponent = props.as; } //if this component was created with an 'as' attribute, //then we combine the dataRequest with the childComponent that this specific instance comes with let instanceDataRequest = props.as && props.as.dataRequest ? [...dataRequest, ...props.as.dataRequest] : dataRequest; let [isLoaded, setIsLoaded] = (0, react_1.useState)(undefined); (0, react_1.useEffect)(() => { //if this property is not bound (if this component is bound we can expect all properties to be loaded by the time it renders) if (!props.isBound && usingStorage) { let cachedRequest = LinkedStorage_1.LinkedStorage.nodesAreLoaded(linkedProps.sources.getNodes(), instanceDataRequest); //if these properties were requested before and have finished loading if (cachedRequest === true) { //we can set state to reflect that setIsLoaded(true); } else if (cachedRequest === false) { //if we did not request all these properties before then we continue to load them all //load the required PropertyShapes from storage for this specific source //we bypass cache because already checked cache ourselves above LinkedStorage_1.LinkedStorage.loadShapes(linkedProps.sources, instanceDataRequest, true).then((quads) => { //set the 'isLoaded' state to true, so we don't need to even check cache again. setIsLoaded(true); }); } else { //if some requiredProperties are still being loaded //cachedResult will be a promise (there is no other return type) //(this may happen when a different component already requested the same properties for the same source just before this sibling component) //wait for that loading to be completed and then update the state cachedRequest.then(() => { setIsLoaded(true); }); } } //note: this useEffect function should be re-triggered if a different set of source nodes is given //however the actual set could be a new one every time. For now we check the 'of' prop, but if this triggers //on every parent update whilst it shouldn't, we could try linkedProps.sources.map(s => s.node.value).join("") }, [props.of, props.isBound]); //we can assume data is loaded if this is a bound component or if the isLoaded state has been set to true let dataIsLoaded = props.isBound || isLoaded || !usingStorage; //But for the first render, when the useEffect has not run yet, //and no this is not a bound component (so it's a top level linkedComponent), //then we still need to manually check cache to avoid a rendering a temporary load icon until useEffect has run (in the case the data is already loaded) if (!props.isBound && typeof isLoaded === 'undefined' && usingStorage) { //only continue to render if the result is true (all required data loaded), // if it's a promise we already deal with that in useEffect() dataIsLoaded = LinkedStorage_1.LinkedStorage.nodesAreLoaded(linkedProps.sources.getNodes(), instanceDataRequest) === true; } //if the data is loaded if (dataIsLoaded) { //if this set component used Shape.requestForEachInSet (instead of Shape.requestSet) if (dataDeclaration && dataDeclaration.request) { //then we provide that request as the getLinkedData prop linkedProps.getLinkedData = function (source) { //Note that tracedDataResponse is the results of processing dataDeclaration.request //this already happened in a previous step, and just like we regard dataDeclaration.request as the childDataRequestFn //we can also regard tracedDataResponse (its processed traced response) as childTracedDataResponse return getLinkedDataResponse(dataDeclaration.request, source, tracedDataResponse); }; } //if the component used a Shape.requestSet() data declaration function else if (dataDeclaration) { //then use that now to get the requested linkedData for this instance linkedProps.linkedData = getLinkedDataResponse(dataDeclaration.request || dataDeclaration.setRequest, linkedProps.sources, tracedDataResponse); } //render the original components with the original + generated properties return functionalComponent(linkedProps); } else { //render loading return (0, react_1.createElement)('div', null, '...'); } }; //attach the 'of(source)' function. Here named bindSetComponentToData() _wrappedComponent.of = bindSetComponentToData.bind(_wrappedComponent, shapeClass, tracedDataResponse, dataDeclaration); //keep a copy of the original for strict checking of equality when compared to _wrappedComponent.original = functionalComponent; _wrappedComponent.dataRequest = dataRequest; //link the wrapped functional component to its shape _wrappedComponent.shape = shapeClass; //IF this component is a function that has a name if (functionalComponent.name) { //then copy the name (have to do it this way, name is protected) Object.defineProperty(_wrappedComponent, 'name', { value: functionalComponent.name, }); //and add the component class of this module to the global tree registerPackageExport(_wrappedComponent); } //NOTE: if it does NOT have a name, the developer will need to manually use registerPackageExport //register the component and its shape registerComponent(_wrappedComponent, shapeClass); return _wrappedComponent; } function linkedComponentClass(shapeClass) { //this is for Components declared with ES Classes //in this case the function we're in will be used as a decorator: @linkedComponent(SomeShapeClass) //class decorators return a function that receives a constructor and returns a constructor. let decoratorFunction = function (constructor) { //add the component class of this module to the global tree registerPackageExport(constructor); //link the shape constructor['shape'] = shapeClass; //register the component and its shape registerComponent(constructor, shapeClass); //return the original class without modifications // return constructor; //only here we have shapeClass as a value (not in LinkedComponentClass) //so here we can return a new class that extends the original class, //but it adds linked properties like sourceShape let wrappedClass = class extends constructor { constructor(props) { let linkedProps = getLinkedComponentProps(props, shapeClass); super(linkedProps); } }; //copy the name Object.defineProperty(wrappedClass, 'name', { value: constructor.name }); Object.defineProperty(wrappedClass, 'original', { value: constructor }); return wrappedClass; }; return decoratorFunction; } //create a declarator function which Shapes of this module can use register themselves and add themselves to the global tree let linkedShape = function (constructor) { //add the component class of this module to the global tree registerPackageExport(constructor); //register the component and its shape Shape_1.Shape.registerByType(constructor); //if no shape object has been attached to the constructor if (!Object.getOwnPropertyNames(constructor).includes('shape')) { let packageNameURI = URI_1.URI.sanitize(packageName); //create a new node shape for this shapeClass let shape = SHACL_1.NodeShape.getFromURI(`${exports.LINCD_DATA_ROOT}module/${packageNameURI}/shape/${URI_1.URI.sanitize(constructor.name)}`); //connect the typescript class to its NodeShape constructor.shape = shape; //set the name shape.label = constructor.name; //also keep track of the reverse: nodeShape to typescript class (helpful for sending shapes between environments with JSONWriter / JSONParser) (0, ShapeClass_1.addNodeShapeToShapeClass)(shape, constructor); //also create a representation in the graph of the shape class itself let shapeClass = models_1.NamedNode.getOrCreate(`${exports.LINCD_DATA_ROOT}module/${packageNameURI}/shapeclass/${URI_1.URI.sanitize(constructor.name)}`, true); shapeClass.set(lincd_1.lincd.definesShape, shape.node); shapeClass.set(rdf_1.rdf.type, lincd_1.lincd.ShapeClass); //and connect it back to the module shapeClass.set(lincd_1.lincd.module, packageNode); //if linkedProperties have already registered themselves if (constructor.propertyShapes) { //then add them to this node shape now constructor.propertyShapes.forEach((propertyShape) => { let uri = shape.namedNode.uri + `/${URI_1.URI.sanitize(propertyShape.label)}`; //with react hot reload, sometimes the same code gets loaded twice //and a node with this URI will already exist, //in that case we can ignore it, since the nodeShape will already have the propertyShape if (!models_1.NamedNode.getNamedNode(uri)) { //update the URI (by extending the URI of the shape) propertyShape.namedNode.uri = shape.namedNode.uri + `/${URI_1.URI.sanitize(propertyShape.label)}`; shape.addPropertyShape(propertyShape); } }); //and remove the temporary key delete constructor.propertyShapes; //TODO replace the above with this newer more general method: if (constructor.shapeCallbacks) { constructor.shapeCallbacks.forEach((callback) => { callback(shape); }); delete constructor.shapeCallbacks; } } //if property shapes referred to this node shape as the required shape for their values // (note that accessor decorators always evaluate before class decorators, hence we sometimes need to process this here, AFTER the property decorators have run) if (constructor.nodeShapeOf) { constructor.nodeShapeOf.forEach((propertyShape) => { //now that we have a NodeShape for this shape class, we can set the nodeShape of the property shape propertyShape.valueShape = shape; }); } } else { // (constructor.shape.node as NamedNode).uri = URI; 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; }; /** * * @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) { //store specifics in exports. And make sure we can detect this as an ontology later exports['_ns'] = nameSpace; exports['_prefix'] = prefixAndFileName; exports['_load'] = loadData; exports['_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_1.Prefix.add(prefixAndFileName, nameSpace('').uri); } ontologies.add(exports); //register all the exports under the prefix. NOTE: this means the file name HAS to match the prefix registerInPackageTree(prefixAndFileName, exports); // }); if (autoLoadOntologyData) { loadData().catch((err) => { console.warn('Could not load ontology data. Do you need to rebuild the module of the ' + prefixAndFileName + ' ontology?', err); }); } }; //return the declarators so the module can use them return { linkedComponent, linkedComponentClass, linkedSetComponent, linkedShape, linkedUtil, linkedOntology, registerPackageExport, registerPackageModule, packageExports: packageTreeObject, packageName: packageName, }; } function processDataDeclaration(requiredData, functionalComponent, setComponent = false) { let shapeClass; let dataRequest; let tracedDataResponse; let dataDeclaration; //if a Shape class was given (the actual class that extends Shape) if (requiredData['prototype'] instanceof Shape_1.Shape || requiredData === Shape_1.Shape) { //then we will load instances of this shape // and require instances of this shape to be used for this component shapeClass = requiredData; //but we don't specifically request any data dataRequest = []; // dataRequest = shapeClass.shape ? [...shapeClass.shape.getPropertyShapes()] : []; } else { //requiredData is a LinkedDataDeclaration or a LinkedDataSetDeclaration dataDeclaration = requiredData; shapeClass = dataDeclaration.shape; //create a test instance of the shape let dummyInstance = createTraceShape(shapeClass, null, functionalComponent.name || functionalComponent.toString().substring(0, 80) + ' ...'); //if setComponent is true then linkedSetComponent() was used //if then also dataDeclaration.setRequest is defined, then the SetComponent used Shape.requestSet() //and its LinkedDataRequestFn expects a set of shape instances //otherwise (also for Shape.requestForEachInSet) it expects a single shape instance let provideSetToDataRequestFn = setComponent && dataDeclaration.setRequest; let dummySet = provideSetToDataRequestFn ? new ShapeSet_1.ShapeSet([dummyInstance]) : dummyInstance; //create a dataRequest object, we will use this for requesting data from stores [dataRequest, tracedDataResponse] = createDataRequestObject(dataDeclaration.request || dataDeclaration.setRequest, dummySet); } return [shapeClass, dataRequest, dataDeclaration, tracedDataResponse]; } function getLinkedDataResponse(dataRequestFn, source, tracedDataResponse) { let dataResponse = dataRequestFn(source); let replaceBoundComponent = (value, key) => { //if a function was returned for this key if (typeof value === 'function') { //then call the function to get the actual intended value let evaluated = value(); //if a bound component was returned if (evaluated && evaluated._create) { //here we get the propertyShapes that were required to obtain the source // when we evaluated the function when the component was first initialised let props = key ? tracedDataResponse[key]._props : tracedDataResponse._props; evaluated = evaluated._create(props); } return evaluated; } return value; }; if (Array.isArray(dataResponse)) { dataResponse.forEach((value, index) => { dataResponse[index] = replaceBoundComponent(value, index); }); } else if (typeof dataResponse === 'function') { dataResponse = replaceBoundComponent(dataResponse); } else { Object.getOwnPropertyNames(dataResponse).forEach((key) => { dataResponse[key] = replaceBoundComponent(dataResponse[key], key); }); } return dataResponse; } function replaceSubRequestsFromTraceShapes(traceShape) { //for each propertyShape that was requested traceShape.requested.forEach((propertyShapeRequest, index) => { //get returned value for this requested property shape let value = traceShape.responses[index]; //if the value is a traceShape if (value instanceof Shape_1.Shape) { //then iteratively replace that shapes sub requests replaceSubRequestsFromTraceShapes(value); //and replace the previous value with a new subrequest traceShape.requested[index] = [ propertyShapeRequest, value.requested, ]; } }); } function createDataRequestObject(dataRequestFn, instance) { if (dataRequestFn.of) { return [null, null]; } //run the function that the component provided to see which properties it needs let dataResponse = dataRequestFn(instance); //for sets, we should get a set with a single item, so we can retrieve traced properties from that single item let traceInstance = instance instanceof ShapeSet_1.ShapeSet ? instance.first() : instance; //first finish up the dataRequest object by inserted nested requests from shapes replaceSubRequestsFromTraceShapes(traceInstance); //start with the requested properties, which is an array of PropertyShapes let dataRequest = traceInstance.requested; let insertSubRequestsFromBoundComponent = (value, target, key) => { //if a shape was returned for this key // if (value instanceof Shape) { // //then add all the things that were requested from this shape as a nested sub request // insertSubRequestForTraceShape(dataRequest,key,value as TraceShape); // } //if a function was returned for this key if (typeof value === 'function') { //count the current amount of property shapes requested so far let previousNumPropShapes = traceInstance.requested.length; //then call the function to get the actual intended value //this will also trigger new accessors to be called & property shapes to be added to 'traceInstance.requested' let evaluated = value(); //if a bound component was returned if (evaluated && evaluated._create) { //retrieve and remove any propertyShape that were requested let appliedPropertyShapes = traceInstance.requested.splice(previousNumPropShapes); //store them in the bound component factory object, //we will need that when we actually use the nested component evaluated._props = appliedPropertyShapes; //then place back an object stating which property shapes were requested //and which subRequest need to be made for those as defined by the bound child component let subRequest = evaluated._comp.dataRequest; if (evaluated._childDataRequest) { subRequest = subRequest.concat(evaluated._childDataRequest); } if (appliedPropertyShapes.length > 1) { console.warn('Using multiple property shapes for subRequests are not yet supported'); } let propertyShape = appliedPropertyShapes[0]; //add an entry for the bound property with its subRequest (the data request of the component it was bound to) //if a specific property shape was requested for this component (like CompA(Shape.request(shape => CompB.of(shape.subProperty)) if (propertyShape) { //then add it as a propertyShape + subRequest dataRequest.push([propertyShape, subRequest]); } else { //but if not, then the component uses the SAME source (like CompA(Shape.request(s => CompB.of(s))) //in this case the dataRequest of CompB can be directly added to that of CompA //because its requesting properties of the same subject //this keeps the request plain and simple for the stores that need to resolve it dataRequest = dataRequest.concat(subRequest); } } return evaluated; } return value; }; //whether the component returned an array, a function or an object, replace the values that are bound-component-factories if (Array.isArray(dataResponse)) { dataResponse.forEach((value, index) => { dataResponse[index] = insertSubRequestsFromBoundComponent(value); }); } else if (typeof dataResponse === 'function') { dataResponse = insertSubRequestsFromBoundComponent(dataResponse); } else { Object.getOwnPropertyNames(dataResponse).forEach((key) => { dataResponse[key] = insertSubRequestsFromBoundComponent(dataResponse[key]); }); } return [dataRequest, dataResponse]; } // function addTestDataAccessors(detectionClass,shapeClass,dummyShape): function createTraceShape(shapeClass, shapeInstance, debugName) { let detectionClass = class extends shapeClass { constructor(p) { super(p); this.requested = []; // resultOrigins:CoreMap<any,any> = new CoreMap(); this.usedAccessors = []; this.responses = []; } }; let traceShape; if (!shapeInstance) { //if not provided we create a new detectionClass instance let dummyNode = new TestNode(); traceShape = new detectionClass(dummyNode); } else { //if an instance was provided // (this happens if a testnode generates a testnode value on demand // and the original shape get-accessor returns an instance of a shape of that testnode) //then we turn that shape instance into it's test/detection variant traceShape = new detectionClass(shapeInstance.namedNode); } //here in the constructor (now that we have a 'this') //we will overwrite all the methods of the class we extend and that classes that that extends let finger = shapeClass; while (finger) { //check this superclass still extends Shape, otherwise break; if (!(finger.prototype instanceof Shape_1.Shape) || finger === Shape_1.Shape) { break; } let descriptors = Object.getOwnPropertyDescriptors(finger.prototype); for (var key in descriptors) { let descriptor = descriptors[key]; if (descriptor.configurable) { //if this is a get method that used a @linkedProperty decorator //then it should match with a propertyShape let propertyShape = finger['shape'].getPropertyShapes().find((propertyShape) => propertyShape.label === key); //get the get method (that's the one place that we support @linkedProperty decorators for, for now) let g = descriptor.get != null; if (g) { let newDescriptor = {}; newDescriptor.enumerable = descriptor.enumerable; newDescriptor.configurable = descriptor.configurable; //not sure if we can or want to?.. // newDescriptor.value= descriptor.value; // newDescriptor.writable = descriptor.writable; if (propertyShape) { //create a new get function newDescriptor.get = ((key, propertyShape, descriptor) => { // console.log(debugName + ' requested get ' + key + ' - ' + propertyShape.path.value); //use dummyShape as 'this' let returnedValue = descriptor.get.call(traceShape); // console.log('generated result -> ',res['print'] ? res['print']() : res); // console.log('\tresult -> ', returnedValue && returnedValue.print ? returnedValue.print() : returnedValue); //if a shape was returned, make sure we trace that shape too if (returnedValue instanceof Shape_1.Shape) { returnedValue = createTraceShape(Object.getPrototypeOf(returnedValue).constructor, returnedValue, Object.getPrototypeOf(returnedValue).constructor.name); } //store which property shapes were requested in the detectionClass defined above traceShape.requested.push(propertyShape); traceShape.usedAccessors.push(descriptor.get); traceShape.responses.push(returnedValue); //also store which result was returned for which property shape (we need this in Component.to().. / bindComponentToData()) // traceShape.resultOrigins.set(returnedValue,descriptor.get); // returnedValue['_reqPropShape'] = propertyShape; // returnedValue['_accessor'] = descriptor.get; return returnedValue; }).bind(detectionClass.prototype, key, propertyShape, descriptor); } else { //if no propertyShape was found, then this is a get method that was not decorated with @linkedProperty newDescriptor.get = () => { var _a; let numRequested = traceShape.requested.length; //so we call the method as it was let result = descriptor.get.call(traceShape); //and if no new property shapes have been accessed if (traceShape.requested.length === numRequested) { //then probably someone forgot to add a @linkedProperty decorator! //or at least it won't add any data to the dataRequest of the linked component, so let's warn the developer of that console.warn(`"${(_a = traceShape.nodeShape) === null || _a === void 0 ? void 0 : _a.label}.${descriptor.get.name.replace('get ', '')}" was requested by a linked component. However '${descriptor.get.name}' is not decorated with a linked property decorator (like @linkedProperty), so LINCD can not automatically load this data`); } //(else, the method probably accessed other methods of the shape that DO use linkedProperty decorators, thus adding more traced propertyShapes. This is fine and works as intended) return result; }; } //bind this descriptor to the class that defines it //and bind the required arguments (which we know only now, but we need to know them when the descriptor runs, hence we bind them) //overwrite the get method Object.defineProperty(detectionClass.prototype, key, newDescriptor); } } } finger = Object.getPrototypeOf(finger); } //really we return a TraceShape, but it extends the given Shape class, so we need typescript to recognise it as such //not sure how to do that dynamically return traceShape; } class TestNode extends models_1.NamedNode { constructor(property) { let uri = models_1.NamedNode.createNewTempUri(); super(uri, true); this.property = property; } getValue() { let label = ''; if (this.property) { if (this.property.hasProperty(rdfs_1.rdfs.label)) { label = this.property.getValue(rdfs_1.rdfs.label); } else { label = this.property.uri.split(/[\/#]/).pop(); } } return label; } hasProperty(property) { return true; } getAll(property) { return new NodeSet_1.NodeSet([this.getOne(property)]); } getOne(property) { if (!super.hasProperty(property)) { //test nodes AUTOMATICALLY generate a dummy test-node value when a property is requested //however they avoid sending events about this new models_1.Quad(this, property, new TestNode(property), undefined, false, false); } return super.getOne(property); } } exports.TestNode = TestNode; 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_1.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 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]; } function getSourceFromInputProps(props, shapeClass) { return props.of instanceof models_1.Node ? new shapeClass(props.of) : //if it's a shape it needs to match the shape of the component, or extend it, if not we recreate the shape props.of instanceof Shape_1.Shape && props.of.nodeShape !== shapeClass.shape.node && !(0, ShapeClass_1.hasSuperClass)((0, ShapeClass_1.getShapeClass)(props.of.nodeShape.namedNode), shapeClass) ? new shapeClass(props.of.namedNode) : props.of; } function getLinkedComponentProps(props, shapeClass) { let newProps = Object.assign(Object.assign({}, props), { //if a node was given, convert it to a shape instance source: getSourceFromInputProps(props, shapeClass) }); delete newProps['of']; delete newProps['isBound']; return newProps; } function getLinkedSetComponentProps(props, shapeClass, functionalComponent) { if (!(props.of instanceof NodeSet_1.NodeSet) && !(props.of instanceof ShapeSet_1.ShapeSet) && !props.of['then']) { throw Error("Invalid argument 'of' provided to " + functionalComponent.name.replace('_implementation', '') + ' component: ' + props.of + '. Make sure to provide a NodeSet, a ShapeSet or a Promise resolving to either of those.'); } let newProps = Object.assign(Object.assign({}, props), { //if a NodeSet was given, convert it to a ShapeSet sources: props.of instanceof NodeSet_1.NodeSet ? new ShapeSet_1.ShapeSet(shapeClass.getSetOf(props.of)) : props.of }); delete newProps['of']; return newProps; } 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: {} }; } } function bindComponentToData(tracedDataResponse, source) { return { _comp: this, _create: (propertyShapes) => { let boundComponent = (props) => { //TODO: use propertyShapes for RDFa //add this result as the source of the bound child component let newProps = Object.assign({}, props); newProps['of'] = source; //let the LinkedComponent know that it was bound, //that means it can expect its data to have been loaded by its parent newProps['isBound'] = true; //render the child component (which is 'this') return react_1.default.createElement(this, newProps); }; return boundComponent; }, }; } function bindSetComponentToData(shapeClass, tracedDataResponse, dataDeclaration, sources, childDataRequestFn) { let tracedChildDataResponse; let childDataRequest; //if a childDataRequestFn was given (as second argument of SetComponent.of()) if (childDataRequestFn) { //if a single bound component was given if (childDataRequestFn.of) { //we can access its required properties directly childDataRequest = childDataRequestFn.dataRequest; } else { //else, a child data request function was given as second argument of .of() //then create a test instance of the shape let dummyInstance = createTraceShape(shapeClass, null, this.name || this.toString().substring(0, 60).replace(/\n/g, ' ') + ' ...'); //and run the function that the component provided to see which properties it needs [childDataRequest, tracedChildDataResponse] = createDataRequestObject(childDataRequestFn, dummyInstance); } } return { _comp: this, //we store the childDataRequest (an array of property shapes) so that it can be accessed when stringing together a full data request _childDataRequest: childDataRequest, _create: (propertyShapes) => { let boundComponent = (props) => { //TODO: use propertyShapes for RDFa //manually transfer the value of the first parameter of Component.of() to an of prop in JSX let newProps = Object.assign({}, props); //Note: we use bracket notation here because technically this method is working with InputProps and adding to those input props //But we are setting props to be used in the final LinkedSetComponentProps, so we're using that definition to define other proper