UNPKG

lincd

Version:

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

376 lines 14.5 kB
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { describe, expect, test } from '@jest/globals'; import { LinkedStorage } from '../utils/LinkedStorage.js'; import { defaultGraph, Graph, Literal, NamedNode } from '../models.js'; import { QuadSet } from '../collections/QuadSet.js'; import { rdfs } from '../ontologies/rdfs.js'; import { rdf } from '../ontologies/rdf.js'; import { Shape } from '../shapes/Shape.js'; import { QuadArray } from '../collections/QuadArray.js'; import { PropertyShape } from '../shapes/SHACL.js'; import { createLocal, deleteLocal, resolveLocal, updateLocal, } from '../utils/LocalQueryResolver.js'; export class InMemoryStore extends Shape { constructor(n) { super(n); } init() { // console.log('Init ' + this.toString()); if (!this.initPromise) { this.initPromise = this.loadContents(); } return this.initPromise; } loadContents() { //by default an in-memory store starts empty - it has no permanent storage //overwrite this method to change that this.contents = new QuadSet(); return Promise.resolve(this.contents); } /** * returns the contents of the InMemoryStore as a QuadSet * do NOT modify the returned QuadSet directly. Add or remove contents to this store instead */ getContents() { return this.contents; } updateQuery(query) { return Promise.resolve(updateLocal(query)); } createQuery(q) { return Promise.resolve(createLocal(q)); } deleteQuery(query) { return Promise.resolve(deleteLocal(query)); } update(toAdd, toRemove) { return this.init().then(() => { if (toAdd) { this._addMultiple(toAdd); } if (toRemove) { this._deleteMultiple(toRemove); } //this method should only get called if either toAdd or toRemove is not empty return this.onContentsUpdated(); }); } getDefaultGraph() { //NOTE: we changed this from a specific graph for each store BACK null, which means things will be stored in the default graph // because storing quads in multiple graphs easily becomes error-prone. // Removing a triple from one store might not remove it from the main graph. // for example, auth will store users serialised as JSONLD in sessions, these quads get added back to the main graph // until we have a better solution for this, we aim to only use the default graph as much as possible (which means no specific graph os used, thus return null) // return null; // return defaultGraph; return Graph.getOrCreate(this.namedNode.uri); } add(quad) { return this.init().then(() => { this.addNewContents(new QuadArray(quad)); this.onContentsUpdated(); return Promise.resolve(true); }); } addMultiple(quads) { return this.init().then(() => { this._addMultiple(quads); this.onContentsUpdated(); return Promise.resolve(true); }); } delete(quad) { return this.init().then(() => { // this.contents.delete(quad); this._deleteMultiple(new QuadArray(quad)); this.onContentsUpdated(); return true; }); } deleteMultiple(quads) { return this.init().then(() => { this._deleteMultiple(quads); this.onContentsUpdated(); return true; }); } clearProperties(subjectToPredicates) { return this.init().then(() => { let toDelete = new QuadSet(); this.contents.forEach((q) => { if (subjectToPredicates.has(q.subject) && subjectToPredicates.get(q.subject).has(q.predicate)) { toDelete.add(q); } }); if (toDelete.size > 0) { return this.deleteMultiple(toDelete); } return false; }); } setURIs(nodeToCurrentUriMap) { //by default an in-memory store does not support setting URIs, // if it's used in a local context for in memory storage it doesn't really care about permanent storage URI's // if it's used in a different context, this method should be overwritten return Promise.resolve([]); } removeNodes(nodes) { //when storage calls removeNodes, all quads have already been removed locally //and an in memory store always holds all data in memory, //so there is nothing to do here return Promise.resolve(true); } selectQuery(query) { return Promise.resolve(resolveLocal(query)).catch((e) => { console.error('Error in query', e); return new QuadArray(); }); } loadShape(shapeInstance, request) { return this.init().then(() => { return this.getRequestQuads(shapeInstance, request); //for testing: add timer // return new Promise((resolve, reject) => { // setTimeout(() => { //get quads for each (nested) shape / property shape // let quads = this.getRequestQuads(shapeInstance, request); // return resolve(quads); // }, 1500); // }); }); } loadShapes(shapeInstances, request) { return this.init().then(() => { let quads = new QuadArray(); shapeInstances.forEach((shapeInstance) => { this.getRequestQuads(shapeInstance, request, quads); }); return quads; // return new Promise((resolve, reject) => { // setTimeout(() => { // //get quads for each (nested) shape / property shape // //TODO: update getRequestQuads to work with shape set // // possibly even merge definitions of loadShape and loadShapes? is it select? query? // // let quads = this.getRequestQuads(shapeInstances, request); // // return Promise.resolve(quads); // // return resolve(quads); // return resolve([] as any); // }, 1500); // }); }); } onContentsUpdated() { //by default in memory store does nothing here. But extending classes could choose to sync to a more permanent form of storage return Promise.resolve(false); } addNewContents(quads) { //get the target graph for this store, if configured let graph = LinkedStorage.getGraphForStore(this) || this.targetGraph || defaultGraph; //if there is one, move the quads into that graph if (graph) { quads = quads.moveTo(graph, false); } this.contents.addFrom(quads); return quads; } _addMultiple(quads) { this.addNewContents(quads); // this.contents = this.contents.concat(quads); } _deleteMultiple(quads) { //first we add the quads to the right graph (which effectively ADDS these quads to this store) //then we remove them from the contents //get the target graph for this store, if configured let graph = LinkedStorage.getGraphForStore(this) || this.targetGraph || defaultGraph; //if there is one, move the quads into that graph if (graph) { quads = quads.moveTo(graph, false); } quads.forEach((quad) => { this.contents.delete(quad); quad.remove(false); }); } getRequestQuads(source, request, quads = new QuadArray()) { // let {shape, properties}: {shape: typeof Shape; properties?: (PropertyShape | BoundPropertyShapes)[]} = request; request.forEach((propertyRequest) => { let subRequest; let propertyShape; let propertyShapeSource; //if an entry is an array, then it consists of the property shape + a sub request if (Array.isArray(propertyRequest)) { [propertyShape, subRequest] = propertyRequest; } else if (propertyRequest instanceof PropertyShape) { propertyShape = propertyRequest; } if (propertyShape) { let path = propertyShape.path; if (path instanceof NamedNode) { if (source instanceof QuadSet) { propertyShapeSource = source .getObjects() .getQuads(path); } else if (source instanceof Shape) { propertyShapeSource = source.getQuads(path); } } else { propertyShapeSource = source; for (let i = 0; i < path.length; i++) { if (propertyShapeSource instanceof QuadSet) { propertyShapeSource = propertyShapeSource .getObjects() .getQuads(path[i]); } else if (propertyShapeSource instanceof Shape) { propertyShapeSource = propertyShapeSource.getQuads(path[i]); } } } propertyShapeSource.forEach((q) => quads.push(q)); } if (subRequest) { this.getRequestQuads(propertyShapeSource, subRequest, quads); } }); return quads; } } export class TestStore { constructor() { this.defaultGraph = Graph.create(); this.contents = new QuadSet(); } init() { return null; } reset() { this.contents = new QuadSet(); } update(added, removed) { added.forEach((q) => this.contents.add(q)); removed.forEach((q) => this.contents.delete(q)); return null; } selectQuery(query) { return null; } add(quad) { return null; } addMultiple(quads) { return null; } delete(quad) { return null; } deleteMultiple(quads) { return null; } setURIs(nodeToCurrentUriMap) { return null; } getDefaultGraph() { return this.defaultGraph; } removeNodes(nodes) { return null; } loadShape(shapeInstance, request) { return null; } loadShapes(shapeSet, request) { return null; } clearProperties(subjectToPredicates) { let deleted = false; this.contents.forEach((q) => { if (subjectToPredicates.has(q.subject) && subjectToPredicates.get(q.subject).has(q.predicate)) { this.contents.delete(q); deleted = true; } }); return Promise.resolve(deleted); } } let store = new TestStore(); LinkedStorage.setDefaultStore(store); describe('default store', () => { test('does not store temporary node', () => { let node = NamedNode.create(); node.setValue(rdfs.label, 'test'); expect(store.contents.size).toBe(0); }); test('stores quads of saved node', () => __awaiter(void 0, void 0, void 0, function* () { store.reset(); let node = NamedNode.create(); node.setValue(rdfs.label, 'test2'); node.save(); try { yield LinkedStorage.promiseUpdated(); expect(store.contents.size).toBe(1); } catch (e) { console.warn('Why err?', e); } })); test('stores new properties of existing node', () => __awaiter(void 0, void 0, void 0, function* () { store.reset(); let node = NamedNode.create(); node.isTemporaryNode = false; node.setValue(rdfs.label, 'test3'); yield LinkedStorage.promiseUpdated(); expect(store.contents.size).toBe(1); })); test('unsetAll removes those properties from store', () => __awaiter(void 0, void 0, void 0, function* () { store.reset(); let node = NamedNode.create(); node.isTemporaryNode = false; node.setValue(rdfs.label, 'test4'); yield LinkedStorage.promiseUpdated(); node.unsetAll(rdfs.label); yield LinkedStorage.promiseUpdated(); expect(store.contents.size).toBe(0); })); test('unset removes that property from store', () => __awaiter(void 0, void 0, void 0, function* () { store.reset(); let node = NamedNode.create(); node.isTemporaryNode = false; node.setValue(rdfs.label, 'test4'); yield LinkedStorage.promiseUpdated(); node.unset(rdfs.label, new Literal('test4')); yield LinkedStorage.promiseUpdated(); expect(store.contents.size).toBe(0); })); test('remove node & properties from store', () => __awaiter(void 0, void 0, void 0, function* () { store.reset(); let node = NamedNode.create(); node.isTemporaryNode = false; node.setValue(rdfs.label, 'test5'); yield LinkedStorage.promiseUpdated(); node.remove(); yield LinkedStorage.promiseUpdated(); expect(store.contents.size).toBe(0); })); test('promiseUpdated waits for both storing nodes and altering nodes to complete', () => __awaiter(void 0, void 0, void 0, function* () { store.reset(); let node = NamedNode.create(); node.setValue(rdfs.label, 'test5'); node.save(); node.set(rdf.type, node); yield LinkedStorage.promiseUpdated(); expect(store.contents.size).toBe(2); })); }); //# sourceMappingURL=storage.test.js.map