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