UNPKG

jinaga

Version:

Data management for web and mobile applications.

169 lines (139 loc) 6.26 kB
import { generateKeyPair, KeyPair, signFacts } from "../cryptography/key-pair"; import { computeHash } from "../fact/hash"; import { Fork } from "../fork/fork"; import { PersistentFork } from "../fork/persistent-fork"; import { ObservableSource, SpecificationListener } from "../observable/observable"; import { Observer, ObserverImpl, ResultAddedFunc } from "../observer/observer"; import { testSpecificationForCompliance } from "../purge/purgeCompliance"; import { Specification } from "../specification/specification"; import { FactEnvelope, FactRecord, FactReference, ProjectedResult, Storage } from "../storage"; import { Trace } from "../util/trace"; import { Network, NetworkManager } from "./NetworkManager"; import { PurgeManager } from "./PurgeManager"; export class FactManager { private networkManager: NetworkManager; private purgeManager: PurgeManager; private singleUseKeyPair: KeyPair | null = null; constructor( private readonly fork: Fork, private readonly observableSource: ObservableSource, private readonly store: Storage, network: Network, private readonly purgeConditions: Specification[], feedRefreshIntervalSeconds?: number ) { this.networkManager = new NetworkManager(network, store, factsAdded => this.factsAdded(factsAdded), feedRefreshIntervalSeconds); this.purgeManager = new PurgeManager(store, purgeConditions); } addSpecificationListener(specification: Specification, onResult: (results: ProjectedResult[]) => Promise<void>): SpecificationListener { return this.observableSource.addSpecificationListener(specification, onResult); } removeSpecificationListener(listener: SpecificationListener): void { this.observableSource.removeSpecificationListener(listener); } async close(): Promise<void> { await this.fork.close(); await this.store.close(); } testSpecificationForCompliance(specification: Specification): string[] { return testSpecificationForCompliance(specification, this.purgeConditions); } async save(envelopes: FactEnvelope[]): Promise<FactEnvelope[]> { // If we have a single-use key pair, sign the facts with it if (this.singleUseKeyPair) { envelopes = signFacts(this.singleUseKeyPair, envelopes.map(e => e.fact)); } await this.fork.save(envelopes); const saved = await this.store.save(envelopes); if (saved.length > 0) { Trace.counter("facts_saved", saved.length); await this.factsAdded(saved); } return saved; } /** * Begin a single-use session. Generates a key pair, creates a User fact with the public key, * signs it, and saves it to the store. * @returns A structure containing the user fact */ async beginSingleUse(): Promise<{ graph: any, last: FactRecord }> { // Generate a key pair for the single-use principal const keyPair = generateKeyPair(); // Create a User fact with the public key const fields = { publicKey: keyPair.publicPem }; const predecessors = {}; const hash = computeHash(fields, predecessors); const userFact: FactRecord = { hash, type: "Jinaga.User", fields, predecessors }; // Sign the user fact with the key pair const signedEnvelopes = signFacts(keyPair, [userFact]); // Save the user fact await this.store.save(signedEnvelopes); // Store the key pair temporarily this.singleUseKeyPair = keyPair; // Return a structure containing the user fact return { graph: { facts: [userFact] }, last: userFact }; } /** * End a single-use session. Discards the private key. */ endSingleUse(): void { // Discard the private key this.singleUseKeyPair = null; } async read(start: FactReference[], specification: Specification): Promise<ProjectedResult[]> { this.purgeManager.checkCompliance(specification); return await this.store.read(start, specification); } async fetch(start: FactReference[], specification: Specification) { this.purgeManager.checkCompliance(specification); await this.networkManager.fetch(start, specification); } async subscribe(start: FactReference[], specification: Specification) { this.purgeManager.checkCompliance(specification); return await this.networkManager.subscribe(start, specification); } unsubscribe(feeds: string[]) { this.networkManager.unsubscribe(feeds); } async load(references: FactReference[]): Promise<FactEnvelope[]> { const loaded = await this.fork.load(references); Trace.counter("facts_loaded", loaded.length); return loaded; } getMruDate(specificationHash: string): Promise<Date | null> { return this.store.getMruDate(specificationHash); } setMruDate(specificationHash: string, mruDate: Date): Promise<void> { return this.store.setMruDate(specificationHash, mruDate); } startObserver<U>(references: FactReference[], specification: Specification, resultAdded: ResultAddedFunc<U>, keepAlive: boolean): Observer<U> { const observer = new ObserverImpl<U>(this, references, specification, resultAdded); observer.start(keepAlive); return observer; } private async factsAdded(factsAdded: FactEnvelope[]): Promise<void> { await this.observableSource.notify(factsAdded); await this.purgeManager.triggerPurge(factsAdded); } async purge(): Promise<void> { await this.purgeManager.purge(); } /** * Processes the queue immediately, bypassing any delay. * Only works if the fork is a PersistentFork. */ async push(): Promise<void> { await this.fork.processQueueNow(); } }