jinaga
Version:
Data management for web and mobile applications.
296 lines • 12.3 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.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) {
// 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, label, index) => (Object.assign(Object.assign({}, map), { [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) {
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 || 4 * 60; // Default to 4 minutes
}
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);
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