UNPKG

jinaga

Version:

Data management for web and mobile applications.

296 lines 12.4 kB
"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.NetworkManager = exports.NetworkDistribution = exports.NetworkNoOp = void 0; const subscriber_1 = require("../observer/subscriber"); const description_1 = require("../specification/description"); const feed_builder_1 = require("../specification/feed-builder"); const feed_cache_1 = require("../specification/feed-cache"); const specification_1 = require("../specification/specification"); const storage_1 = require("../storage"); const encoding_1 = require("../util/encoding"); const trace_1 = require("../util/trace"); class NetworkNoOp { feeds(start, specification) { return Promise.resolve([]); } fetchFeed(feed, bookmark) { return Promise.resolve({ references: [], bookmark }); } streamFeed(feed, bookmark, onResponse, onError, feedRefreshIntervalSeconds) { // Do nothing. return () => { }; } load(factReferences) { return Promise.resolve([]); } } exports.NetworkNoOp = NetworkNoOp; class NetworkDistribution { constructor(distributionEngine, user) { this.distributionEngine = distributionEngine; this.user = user; this.feedCache = new feed_cache_1.FeedCache(); } feeds(start, specification) { return __awaiter(this, void 0, void 0, function* () { const feeds = (0, feed_builder_1.buildFeeds)(specification); const namedStart = specification.given.reduce((map, given, index) => (Object.assign(Object.assign({}, map), { [given.label.name]: start[index] })), {}); const canDistribute = yield this.distributionEngine.canDistributeToAll(feeds, namedStart, this.user); if (canDistribute.type === 'failure') { throw new Error(`Not authorized: ${canDistribute.reason}`); } return this.feedCache.addFeeds(feeds, namedStart); }); } fetchFeed(feed, bookmark) { return __awaiter(this, void 0, void 0, function* () { const feedObject = this.feedCache.getFeed(feed); if (!feedObject) { throw new Error(`Feed ${feed} not found`); } const canDistribute = yield this.distributionEngine.canDistributeToAll([feedObject.feed], feedObject.namedStart, this.user); if (canDistribute.type === 'failure') { throw new Error(`Not authorized: ${canDistribute.reason}`); } // Pretend that we are at the end of the feed. return { references: [], bookmark }; }); } streamFeed(feed, bookmark, onResponse, onError, feedRefreshIntervalSeconds) { const feedObject = this.feedCache.getFeed(feed); if (!feedObject) { onError(new Error(`Feed ${feed} not found`)); return () => { }; } this.distributionEngine.canDistributeToAll([feedObject.feed], feedObject.namedStart, this.user) .then(canDistribute => { if (canDistribute.type === 'failure') { onError(new Error(`Not authorized: ${canDistribute.reason}`)); return; } // Pretend that we are at the end of the feed. onResponse([], bookmark); }) .catch(err => { onError(err); }); return () => { }; } load(factReferences) { return Promise.resolve([]); } } exports.NetworkDistribution = NetworkDistribution; class LoadBatch { constructor(network, store, notifyFactsAdded, onRun) { this.network = network; this.store = store; this.notifyFactsAdded = notifyFactsAdded; this.onRun = onRun; this.factReferences = []; this.started = false; this.completed = new Promise((resolve, reject) => { this.resolve = resolve; this.reject = reject; }); this.timeout = setTimeout(() => { this.run(); this.onRun(); }, 100); } add(factReferences) { for (const fr of factReferences) { if (!this.factReferences.some((0, storage_1.factReferenceEquals)(fr))) { this.factReferences.push(fr); } } } trigger() { clearTimeout(this.timeout); this.run(); this.onRun(); } run() { if (!this.started) { this.load() .then(this.resolve) .catch(this.reject); this.started = true; } } load() { return __awaiter(this, void 0, void 0, function* () { const graph = yield this.network.load(this.factReferences); const factsAdded = yield this.store.save(graph); if (factsAdded.length > 0) { trace_1.Trace.counter("facts_saved", factsAdded.length); yield this.notifyFactsAdded(factsAdded); } }); } } class NetworkManager { constructor(network, store, notifyFactsAdded, feedRefreshIntervalSeconds) { this.network = network; this.store = store; this.notifyFactsAdded = notifyFactsAdded; this.feedsCache = new Map(); this.activeFeeds = new Map(); this.fetchCount = 0; this.currentBatch = null; this.subscribers = new Map(); this.feedRefreshIntervalSeconds = feedRefreshIntervalSeconds || 90; // Default to 90 seconds } fetch(start, specification) { return __awaiter(this, void 0, void 0, function* () { const reducedSpecification = (0, specification_1.reduceSpecification)(specification); const feeds = yield this.getFeedsFromCache(start, reducedSpecification); // Fork to fetch from each feed. const promises = feeds.map(feed => { if (this.activeFeeds.has(feed)) { return this.activeFeeds.get(feed); } else { const promise = this.processFeed(feed); this.activeFeeds.set(feed, promise); return promise; } }); try { yield Promise.all(promises); } catch (e) { // If any feed fails, then remove the specification from the cache. this.removeFeedsFromCache(start, reducedSpecification); throw e; } }); } subscribe(start, specification) { return __awaiter(this, void 0, void 0, function* () { const reducedSpecification = (0, specification_1.reduceSpecification)(specification); const feeds = yield this.getFeedsFromCache(start, reducedSpecification); const subscribers = feeds.map(feed => { let subscriber = this.subscribers.get(feed); if (!subscriber) { subscriber = new subscriber_1.Subscriber(feed, this.network, this.store, this.notifyFactsAdded, this.feedRefreshIntervalSeconds); this.subscribers.set(feed, subscriber); } return subscriber; }); const promises = subscribers.map((subscriber) => __awaiter(this, void 0, void 0, function* () { if (subscriber.addRef()) { yield subscriber.start(); } })); try { yield Promise.all(promises); } catch (e) { // If any feed fails, then remove the specification from the cache. this.removeFeedsFromCache(start, reducedSpecification); this.unsubscribe(feeds); throw e; } return feeds; }); } unsubscribe(feeds) { for (const feed of feeds) { const subscriber = this.subscribers.get(feed); if (!subscriber) { throw new Error(`Subscriber not found for feed ${feed}`); } if (subscriber.release()) { subscriber.stop(); this.subscribers.delete(feed); } } } getFeedsFromCache(start, specification) { return __awaiter(this, void 0, void 0, function* () { const hash = getSpecificationHash(start, specification); const cached = this.feedsCache.get(hash); if (cached) { return cached; } const feeds = yield this.network.feeds(start, specification); this.feedsCache.set(hash, feeds); return feeds; }); } removeFeedsFromCache(start, specification) { const hash = getSpecificationHash(start, specification); this.feedsCache.delete(hash); } processFeed(feed) { return __awaiter(this, void 0, void 0, function* () { let bookmark = yield this.store.loadBookmark(feed); while (true) { this.fetchCount++; let decremented = false; try { const { references: factReferences, bookmark: nextBookmark } = yield this.network.fetchFeed(feed, bookmark); if (factReferences.length === 0) { break; } const knownFactReferences = yield this.store.whichExist(factReferences); const unknownFactReferences = factReferences.filter(fr => !knownFactReferences.includes(fr)); if (unknownFactReferences.length > 0) { let batch = this.currentBatch; if (batch === null) { // Begin a new batch. batch = new LoadBatch(this.network, this.store, this.notifyFactsAdded, () => { if (this.currentBatch === batch) { this.currentBatch = null; } }); this.currentBatch = batch; } batch.add(unknownFactReferences); this.fetchCount--; decremented = true; if (this.fetchCount === 0) { // This is the last fetch, so trigger the batch. batch.trigger(); } yield batch.completed; } bookmark = nextBookmark; yield this.store.saveBookmark(feed, bookmark); } finally { if (!decremented) { this.fetchCount--; if (this.fetchCount === 0 && this.currentBatch !== null) { // This is the last fetch, so trigger the batch. this.currentBatch.trigger(); } } } } this.activeFeeds.delete(feed); }); } } exports.NetworkManager = NetworkManager; function getSpecificationHash(start, specification) { const declarationString = (0, description_1.describeDeclaration)(start, specification.given.map(g => g.label)); const specificationString = (0, description_1.describeSpecification)(specification, 0); const request = `${declarationString}\n${specificationString}`; const hash = (0, encoding_1.computeStringHash)(request); return hash; } //# sourceMappingURL=NetworkManager.js.map