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
JavaScript
"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