jinaga
Version:
Data management for web and mobile applications.
216 lines • 9.61 kB
JavaScript
"use strict";
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());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MemoryStore = exports.getPredecessors = void 0;
const hydrate_1 = require("../fact/hydrate");
const specification_runner_1 = require("../specification/specification-runner");
const storage_1 = require("../storage");
function getPredecessors(fact, role) {
if (!fact) {
return [];
}
const predecessors = fact.predecessors[role];
if (predecessors) {
if (Array.isArray(predecessors)) {
return predecessors;
}
else {
return [predecessors];
}
}
else {
return [];
}
}
exports.getPredecessors = getPredecessors;
function loadAll(references, source, target) {
references.forEach(reference => {
const predicate = (0, storage_1.factEnvelopeEquals)(reference);
if (!target.some(predicate)) {
const record = source.find(predicate);
if (record) {
target.push(record);
for (const role in record.fact.predecessors) {
const predecessors = getPredecessors(record.fact, role);
loadAll(predecessors, source, target);
}
}
}
});
}
class MemoryStore {
constructor(timeProvider) {
this.factEnvelopes = [];
this.bookmarksByFeed = {};
this.mruDateBySpecificationHash = {};
this.timeProvider = timeProvider !== null && timeProvider !== void 0 ? timeProvider : (() => new Date());
this.runner = new specification_runner_1.SpecificationRunner({
getPredecessors: this.getPredecessors.bind(this),
getSuccessors: this.getSuccessors.bind(this),
findFact: this.findFact.bind(this),
hydrate: this.hydrate.bind(this)
});
}
close() {
return Promise.resolve();
}
save(envelopes) {
const added = [];
const timestamp = this.timeProvider();
for (const envelope of envelopes) {
const isFact = (0, storage_1.factReferenceEquals)(envelope.fact);
const existing = this.factEnvelopes.find(e => isFact(e.fact));
if (!existing) {
// Add timestamp to new facts
const timestampedEnvelope = {
fact: Object.assign(Object.assign({}, envelope.fact), { timestamp }),
signatures: envelope.signatures
};
this.factEnvelopes.push(timestampedEnvelope);
added.push(envelope);
}
else {
// Preserve original timestamp for duplicates
const newSignatures = envelope.signatures.filter(s => !existing.signatures.some(s2 => s2.publicKey === s.publicKey));
if (newSignatures.length > 0) {
existing.signatures = [...existing.signatures, ...newSignatures];
}
}
}
return Promise.resolve(added);
}
read(start, specification) {
return this.runner.read(start, specification);
}
feed(feed, start, _bookmark) {
return __awaiter(this, void 0, void 0, function* () {
// TODO: Implement monotonic bookmarks defined by the store.
// Bookmarks must be monotonically increasing quantities with a store-defined
// format and comparison function. Fact hashes are not monotonic, so prior
// implementations that derived bookmarks from hashes have been removed.
// For now, feeds ignore bookmarks and always return empty bookmark values.
// Compute projected results using the same engine as application reads
const results = yield this.runner.read(start, feed);
// Map each projected result to a tuple of fact references
const tuples = results.map(result => {
const references = Object.values(result.tuple);
const unique = (0, storage_1.uniqueFactReferences)(references);
return { facts: unique, bookmark: '' };
});
return { tuples, bookmark: '' };
});
}
whichExist(references) {
const existing = references.filter(reference => {
return this.factEnvelopes.some((0, storage_1.factEnvelopeEquals)(reference));
});
return Promise.resolve(existing);
}
load(references) {
const target = [];
loadAll(references, this.factEnvelopes, target);
return Promise.resolve(target);
}
purge(purgeConditions) {
// Not yet implemented
return Promise.resolve(0);
}
purgeDescendants(purgeRoot, triggers) {
// Remove all facts that are descendants of the purge root
// and not a trigger or an ancestor of a trigger.
const triggersAndTheirAncestors = [...triggers];
for (const trigger of triggers) {
const triggerEnvelope = this.factEnvelopes.find((0, storage_1.factEnvelopeEquals)(trigger));
if (triggerEnvelope) {
this.addAllAncestors(triggerEnvelope.fact, triggersAndTheirAncestors);
}
}
const startingCount = this.factEnvelopes.length;
this.factEnvelopes = this.factEnvelopes.filter(e => {
const ancestors = this.ancestorsOf(e.fact);
return !ancestors.some((0, storage_1.factReferenceEquals)(purgeRoot)) ||
triggersAndTheirAncestors.some((0, storage_1.factReferenceEquals)(e.fact));
});
const endingCount = this.factEnvelopes.length;
return Promise.resolve(startingCount - endingCount);
}
loadBookmark(feed) {
const bookmark = this.bookmarksByFeed.hasOwnProperty(feed) ? this.bookmarksByFeed[feed] : '';
return Promise.resolve(bookmark);
}
saveBookmark(feed, bookmark) {
this.bookmarksByFeed[feed] = bookmark;
return Promise.resolve();
}
getMruDate(specificationHash) {
var _a;
const mruDate = (_a = this.mruDateBySpecificationHash[specificationHash]) !== null && _a !== void 0 ? _a : null;
return Promise.resolve(mruDate);
}
setMruDate(specificationHash, mruDate) {
this.mruDateBySpecificationHash[specificationHash] = mruDate;
return Promise.resolve();
}
findFact(reference) {
var _a, _b;
const envelope = (_a = this.factEnvelopes.find((0, storage_1.factEnvelopeEquals)(reference))) !== null && _a !== void 0 ? _a : null;
return Promise.resolve((_b = envelope === null || envelope === void 0 ? void 0 : envelope.fact) !== null && _b !== void 0 ? _b : null);
}
getPredecessors(reference, name, predecessorType) {
var _a;
const record = (_a = this.factEnvelopes.find((0, storage_1.factEnvelopeEquals)(reference))) !== null && _a !== void 0 ? _a : null;
if (record === null) {
// Return empty array instead of throwing when fact is not found
// This allows specifications to handle unpersisted given facts gracefully
return Promise.resolve([]);
}
const predecessors = getPredecessors(record.fact, name);
const matching = predecessors.filter(predecessor => predecessor.type === predecessorType);
return Promise.resolve(matching);
}
getSuccessors(reference, name, successorType) {
const successors = this.factEnvelopes.filter(record => record.fact.type === successorType &&
getPredecessors(record.fact, name).some((0, storage_1.factReferenceEquals)(reference)))
.map(e => e.fact);
return Promise.resolve(successors);
}
ancestorsOf(fact) {
const ancestors = [];
this.addAllAncestors(fact, ancestors);
return ancestors;
}
addAllAncestors(fact, ancestors) {
for (const role in fact.predecessors) {
const predecessors = getPredecessors(fact, role);
predecessors.forEach(predecessor => {
if (!ancestors.some((0, storage_1.factReferenceEquals)(predecessor))) {
ancestors.push(predecessor);
const predecessorRecord = this.factEnvelopes.find((0, storage_1.factEnvelopeEquals)(predecessor));
if (predecessorRecord) {
this.addAllAncestors(predecessorRecord.fact, ancestors);
}
}
});
}
}
hydrate(reference) {
const fact = (0, hydrate_1.hydrateFromTree)([reference], this.factEnvelopes.map(e => e.fact));
if (fact.length === 0) {
return Promise.resolve(undefined);
}
if (fact.length > 1) {
throw new Error(`The fact ${reference} is defined more than once.`);
}
return Promise.resolve(fact[0]);
}
}
exports.MemoryStore = MemoryStore;
//# sourceMappingURL=memory-store.js.map