UNPKG

lincd

Version:

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

223 lines 9.87 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MutationQueryFactory = void 0; const QueryFactory_js_1 = require("./QueryFactory.js"); const SHACL_js_1 = require("../shapes/SHACL.js"); class MutationQueryFactory extends QueryFactory_js_1.QueryFactory { convertUpdateObject(obj, shape, allowTopLevelId = false) { if (typeof obj === 'object' && !(obj instanceof Date) && obj !== null) { if (!allowTopLevelId && 'id' in obj) { throw new Error('You cannot use id in the top level of an update object'); } return this.convertNodeDescription(obj, shape); } else if (typeof obj === 'function') { //TODO throw new Error('Update functions are not implemented yet'); } else { throw new Error('Invalid update object'); } } isSetModification(obj, shape) { // return obj.add || obj.remove; let hasAdd = obj.add; let hasRemove = obj.remove; let numKeysExpected = (hasAdd ? 1 : 0) + (hasRemove ? 1 : 0); let numKeys = Object.getOwnPropertyNames(obj).length; return hasAdd || (hasRemove && numKeysExpected === numKeys); } convertSetModification(obj, shape) { if (!obj.add && !obj.remove) { throw new Error('Set modification should have either add or remove key'); } const res = {}; if (obj.add) { res.$add = this.convertSetAddValue(obj.add, shape); } if (obj.remove) { res.$remove = this.convertSetRemoveValue(obj.remove, shape); } return res; } convertSetRemoveValue(obj, shape) { //the user can either pass an array of node references or a single node reference //either way we should return an array of node reference values if (Array.isArray(obj)) { return obj.map((o) => this.convertSingleRemoveValue(o, shape)); } else { return [this.convertSingleRemoveValue(obj, shape)]; } } convertSetAddValue(obj, shape) { if (Array.isArray(obj)) { return obj.map((o) => this.convertUpdateValue(o, shape)); } else { return [this.convertUpdateValue(obj, shape)]; } } convertSingleRemoveValue(value, shape) { if (this.isNodeReference(value)) { return this.convertNodeReference(value); } else { throw new Error(`Invalid value for ${shape.label}.$remove. Expected an object with an id as key: {id:string}`); } } convertNodeDescription(obj, shape) { const props = shape.getPropertyShapes(true); const fields = []; let id; if (obj && '__id' in obj) { //if the object has a __id key, then we should use that in the result id = obj.__id.toString(); //but we should not include it in the fields delete obj.__id; } for (var key in obj) { let propShape = props.find((p) => p.label === key); if (!propShape) { throw Error(`Invalid property key: ${key}. The shape ${shape.label || shape.uri.split('/').pop()} does not have a registered property with this name. Make sure the get/set method exists, and that it uses a @objectProperty or @literalProperty decorator.`); } else { fields.push(this.createNodePropertyValue(obj[key], propShape)); } } const res = { fields, shape, }; if (id) { res.__id = id; } return res; } createNodePropertyValue(value, propShape) { // let value = obj[propShape.label]; return { prop: propShape, val: this.convertUpdateValue(value, propShape), }; } convertUpdateValue(value, propShape, allowArrays = true) { //single value which will if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean' || value instanceof Date) { return value; } //if multiple items are given as value of this prop if (Array.isArray(value)) { if (!allowArrays) { throw new Error('Nested arrays are not allowed as values of keys'); } //then convert each value, but disallow nested arrays moving forward return value.map((o) => { return this.convertUpdateValue(o, propShape, false); }); } else if (typeof value === 'undefined') { return value; } else if (value === null) { //unsetting a value with null is also possible. But we pass it as undefined in the query object return undefined; } else if (typeof value === 'object') { if (this.isNodeReference(value)) { return this.convertNodeReference(value); } else { let valueShape = propShape.valueShape; //pass the value shape of the property as the node shape of this value if (!propShape.valueShape) { //It's possible to define the shape of the value in the value itself for properties who do not define the shape in their objectProperty if (value.shape) { if (!(value.shape.shape instanceof SHACL_js_1.NodeShape)) { throw new Error(`The value of property "shape" is invalid and should be a class that extends Shape.`); } valueShape = value.shape.shape; } else { //TODO: not sure if this should be an error. Does every @linkedObject need to define the shape of the values? // If not, then how do we continue? because currently we use the value shape to look up further property shapes throw new Error(`Cannot update properties with plain objects if the shape of the values is not known. Make sure get/set ${propShape.parentNodeShape.label}.${propShape.label} defines the 'shape' key in its @objectProperty decorator.`); } } //never keep a shape key in the value object if (value.shape) { //double check that IF a shape value is provided, that it matches the shape from the @objectProperty decorator if (!value.shape.shape.equals(valueShape)) { throw new Error(`The property 'shape' is reserved in LINCD and should not be used here in this way. The ${propShape.label} property already defines the shape of the value as ${propShape.label}. If you want to use a different shape, use the 'shape' key in the @objectProperty decorator.`); } delete value.shape; } if (this.isSetModification(value, propShape)) { return this.convertSetModification(value, propShape); } else { return this.convertNodeDescription(value, valueShape); } // //check if the property shape allows a single value // if(propShape.maxCount === 1) { // //if yes, then the object should be seen as a node description // return this.convertNodeDescription(value,propShape.valueShape); // } else { // if(this.isSetModification(value,propShape)) { // //but if multiple values are allowed, the value should either be an Array of node descriptions // //OR an object with add or remove keys // return this.convertSetModification(value,propShape); // } else { // //it must be a set overwrite, and it must be coming from an array // if(!allowArrays) { // return this.convertNodeDescription(value,propShape.valueShape); // } else { // throw new Error("Invalid array value. Should be a node reference or node description") // } // } // } } } throw new Error(`Unsupported update value type: ${typeof value}`); } isNodeReference(obj) { //check if obj is an object with an id property //if yes, all other properties are ignored return typeof obj === 'object' && obj !== null && 'id' in obj; // && Object.keys(obj).length === 1); //and id is the only property } convertNodeReferences(input) { if (Array.isArray(input)) { return input.map((o) => { return this.convertNodeReferenceOrString(o); }); } else { return [this.convertNodeReferenceOrString(input)]; } } convertNodeReferenceOrString(o) { if (typeof o === 'string') { return { id: o }; } else if (this.isNodeReference(o)) { return this.convertNodeReference(o); } else { throw new Error(`Invalid node reference: ${JSON.stringify(o)}`); } } convertNodeReference(obj) { //ensure there are no other properties in the object // if (Object.keys(obj).length > 1) // { // throw new Error('Cannot have id and other properties in the same value object'); // } return { id: obj.id }; } } exports.MutationQueryFactory = MutationQueryFactory; //# sourceMappingURL=MutationQuery.js.map