UNPKG

jinaga

Version:

Data management for web and mobile applications.

200 lines (176 loc) 8.16 kB
import { hydrateFromTree } from '../fact/hydrate'; import { Specification } from "../specification/specification"; import { SpecificationRunner } from '../specification/specification-runner'; import { FactEnvelope, FactFeed, FactRecord, FactReference, ProjectedResult, Storage, factEnvelopeEquals, factReferenceEquals } from '../storage'; export function getPredecessors(fact: FactRecord | null, role: string) { if (!fact) { return []; } const predecessors = fact.predecessors[role]; if (predecessors) { if (Array.isArray(predecessors)) { return predecessors; } else { return [ predecessors ]; } } else { return []; } } function loadAll(references: FactReference[], source: FactEnvelope[], target: FactEnvelope[]) { references.forEach(reference => { const predicate = 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); } } } }); } export class MemoryStore implements Storage { private factEnvelopes: FactEnvelope[] = []; private bookmarksByFeed: { [feed: string]: string } = {}; private runner: SpecificationRunner; private mruDateBySpecificationHash: { [specificationHash: string]: Date } = {}; constructor() { this.runner = new SpecificationRunner({ getPredecessors: this.getPredecessors.bind(this), getSuccessors: this.getSuccessors.bind(this), findFact: this.findFact.bind(this), hydrate: this.hydrate.bind(this) }); } close(): Promise<void> { return Promise.resolve(); } save(envelopes: FactEnvelope[]): Promise<FactEnvelope[]> { const added: FactEnvelope[] = []; for (const envelope of envelopes) { const isFact = factReferenceEquals(envelope.fact); const existing = this.factEnvelopes.find(e => isFact(e.fact)); if (!existing) { this.factEnvelopes.push(envelope); added.push(envelope); } else { 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: FactReference[], specification: Specification): Promise<ProjectedResult[]> { return this.runner.read(start, specification); } feed(feed: Specification, start: FactReference[], bookmark: string): Promise<FactFeed> { throw new Error('Method not implemented.'); } whichExist(references: FactReference[]): Promise<FactReference[]> { const existing = references.filter(reference => { return this.factEnvelopes.some(factEnvelopeEquals(reference)); }); return Promise.resolve(existing); } load(references: FactReference[]): Promise<FactEnvelope[]> { const target: FactEnvelope[] = []; loadAll(references, this.factEnvelopes, target); return Promise.resolve(target); } purge(purgeConditions: Specification[]): Promise<number> { // Not yet implemented return Promise.resolve(0); } purgeDescendants(purgeRoot: FactReference, triggers: FactReference[]): Promise<number> { // Remove all facts that are descendants of the purge root // and not a trigger or an ancestor of a trigger. const triggersAndTheirAncestors: FactReference[] = [...triggers]; for (const trigger of triggers) { const triggerEnvelope = this.factEnvelopes.find(factEnvelopeEquals(trigger)); if (triggerEnvelope) { this.addAllAncestors(triggerEnvelope.fact, triggersAndTheirAncestors); } } const startingCount = this.factEnvelopes.length; this.factEnvelopes = this.factEnvelopes.filter(e => { const ancestors: FactReference[] = this.ancestorsOf(e.fact); return !ancestors.some(factReferenceEquals(purgeRoot)) || triggersAndTheirAncestors.some(factReferenceEquals(e.fact)); }); const endingCount = this.factEnvelopes.length; return Promise.resolve(startingCount - endingCount); } loadBookmark(feed: string): Promise<string> { const bookmark = this.bookmarksByFeed.hasOwnProperty(feed) ? this.bookmarksByFeed[feed] : ''; return Promise.resolve(bookmark); } saveBookmark(feed: string, bookmark: string): Promise<void> { this.bookmarksByFeed[feed] = bookmark; return Promise.resolve(); } getMruDate(specificationHash: string): Promise<Date | null> { const mruDate: Date | null = this.mruDateBySpecificationHash[specificationHash] ?? null; return Promise.resolve(mruDate); } setMruDate(specificationHash: string, mruDate: Date): Promise<void> { this.mruDateBySpecificationHash[specificationHash] = mruDate; return Promise.resolve(); } private findFact(reference: FactReference): Promise<FactRecord | null> { const envelope = this.factEnvelopes.find(factEnvelopeEquals(reference)) ?? null; return Promise.resolve(envelope?.fact ?? null); } private getPredecessors(reference: FactReference, name: string, predecessorType: string): Promise<FactReference[]> { const record = this.factEnvelopes.find(factEnvelopeEquals(reference)) ?? null; if (record === null) { throw new Error(`The fact ${reference.type}:${reference.hash} is not defined.`); } const predecessors = getPredecessors(record.fact, name); const matching = predecessors.filter(predecessor => predecessor.type === predecessorType); return Promise.resolve(matching); } private getSuccessors(reference: FactReference, name: string, successorType: string): Promise<FactReference[]> { const successors = this.factEnvelopes.filter(record => record.fact.type === successorType && getPredecessors(record.fact, name).some(factReferenceEquals(reference))) .map(e => e.fact); return Promise.resolve(successors); } private ancestorsOf(fact: FactRecord): FactReference[] { const ancestors: FactReference[] = []; this.addAllAncestors(fact, ancestors); return ancestors; } private addAllAncestors(fact: FactRecord, ancestors: FactReference[]) { for (const role in fact.predecessors) { const predecessors = getPredecessors(fact, role); predecessors.forEach(predecessor => { if (!ancestors.some(factReferenceEquals(predecessor))) { ancestors.push(predecessor); const predecessorRecord = this.factEnvelopes.find(factEnvelopeEquals(predecessor)); if (predecessorRecord) { this.addAllAncestors(predecessorRecord.fact, ancestors); } } }); } } private hydrate(reference: FactReference) { const fact = hydrateFromTree([reference], this.factEnvelopes.map(e => e.fact)); if (fact.length === 0) { throw new Error(`The fact ${reference} is not defined.`); } if (fact.length > 1) { throw new Error(`The fact ${reference} is defined more than once.`); } return Promise.resolve(fact[0]); } }