jinaga
Version:
Data management for web and mobile applications.
136 lines • 7.14 kB
JavaScript
;
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.IndexedDBQueue = void 0;
const driver_1 = require("./driver");
const fn_1 = require("../util/fn");
const sorter_1 = require("../fact/sorter");
class IndexedDBQueue {
constructor(indexName) {
this.indexName = indexName;
}
peek() {
return (0, driver_1.withDatabase)(this.indexName, db => (0, driver_1.withTransaction)(db, ['queue', 'fact', 'ancestor'], 'readonly', (tx) => __awaiter(this, void 0, void 0, function* () {
const queueObjectStore = tx.objectStore('queue');
const factObjectStore = tx.objectStore('fact');
const ancestorObjectStore = tx.objectStore('ancestor');
// Get all envelopes from the queue
const queuedEnvelopes = yield (0, driver_1.execRequest)(queueObjectStore.getAll());
// If queue is empty, return empty array
if (queuedEnvelopes.length === 0) {
return [];
}
// Get references to all queued facts
const queuedReferences = queuedEnvelopes.map(envelope => ({
type: envelope.fact.type,
hash: envelope.fact.hash
}));
// Get all ancestors of queued facts
const allAncestors = yield (0, fn_1.flattenAsync)(queuedReferences, reference => (0, driver_1.execRequest)(ancestorObjectStore.get((0, driver_1.factKey)(reference))));
// Remove duplicates and queued facts from ancestors
const queuedKeys = new Set(queuedReferences.map(driver_1.factKey));
const distinctAncestorKeys = allAncestors
.filter(fn_1.distinct)
.filter(key => !queuedKeys.has(key));
// Get fact records for all ancestors
const ancestorEnvelopes = yield Promise.all(distinctAncestorKeys.map((key) => __awaiter(this, void 0, void 0, function* () {
return ({
fact: yield (0, driver_1.execRequest)(factObjectStore.get(key)),
signatures: []
});
})));
// Combine queued envelopes and ancestor envelopes
const allEnvelopes = [...queuedEnvelopes, ...ancestorEnvelopes];
// Sort envelopes in topological order
return this.sortAndValidateEnvelopes(allEnvelopes);
})));
}
sortAndValidateEnvelopes(envelopes) {
// Extract facts from envelopes
const facts = envelopes.map(envelope => envelope.fact);
// Create a map from fact key to envelope for quick lookup
const envelopeMap = new Map();
envelopes.forEach(envelope => {
envelopeMap.set((0, driver_1.factKey)(envelope.fact), envelope);
});
// Use TopologicalSorter to sort facts
const sorter = new sorter_1.TopologicalSorter();
const sortedKeys = sorter.sort(facts, (_, fact) => (0, driver_1.factKey)(fact));
// Check if sorting was successful (no circular dependencies)
if (!sorter.finished()) {
throw new Error(`Circular dependencies detected in the fact queue. Some facts have predecessors that depend on them, creating a cycle.`);
}
// Validate the topological ordering
this.validateTopologicalOrdering(sortedKeys, facts);
// Convert sorted keys back to envelopes
return sortedKeys.map(key => envelopeMap.get(key));
}
validateTopologicalOrdering(sortedKeys, facts) {
// Create a map of fact keys to their positions in the sorted list
const positionMap = new Map();
sortedKeys.forEach((key, index) => {
positionMap.set(key, index);
});
// Create a map of fact keys to facts for quick lookup
const factMap = new Map();
facts.forEach(fact => {
factMap.set((0, driver_1.factKey)(fact), fact);
});
// Validate that for each fact, all its predecessors appear earlier in the list
for (let i = 0; i < sortedKeys.length; i++) {
const key = sortedKeys[i];
const fact = factMap.get(key);
if (!fact) {
throw new Error(`Internal error: Fact with key ${key} not found in fact map.`);
}
const predecessors = this.getAllPredecessors(fact);
for (const predecessor of predecessors) {
const predKey = (0, driver_1.factKey)(predecessor);
const predPosition = positionMap.get(predKey);
// If predecessor is not in the list, it's an error
if (predPosition === undefined) {
throw new Error(`Missing predecessor: ${predKey} for fact ${key}`);
}
// If predecessor appears after the current fact, it's a topological ordering violation
if (predPosition >= i) {
throw new Error(`Topological ordering violation: ${predKey} (position ${predPosition}) should appear before ${key} (position ${i})`);
}
}
}
}
getAllPredecessors(fact) {
const predecessors = [];
for (const role in fact.predecessors) {
const references = fact.predecessors[role];
if (Array.isArray(references)) {
predecessors.push(...references);
}
else {
predecessors.push(references);
}
}
return predecessors;
}
enqueue(envelopes) {
return (0, driver_1.withDatabase)(this.indexName, db => (0, driver_1.withTransaction)(db, ['queue'], 'readwrite', (tx) => __awaiter(this, void 0, void 0, function* () {
const queueObjectStore = tx.objectStore('queue');
yield Promise.all(envelopes.map(envelope => (0, driver_1.execRequest)(queueObjectStore.put(envelope, (0, driver_1.factKey)(envelope.fact)))));
})));
}
dequeue(envelopes) {
return (0, driver_1.withDatabase)(this.indexName, db => (0, driver_1.withTransaction)(db, ['queue'], 'readwrite', (tx) => __awaiter(this, void 0, void 0, function* () {
const queueObjectStore = tx.objectStore('queue');
yield Promise.all(envelopes.map(envelope => (0, driver_1.execRequest)(queueObjectStore.delete((0, driver_1.factKey)(envelope.fact)))));
})));
}
}
exports.IndexedDBQueue = IndexedDBQueue;
//# sourceMappingURL=indexeddb-queue.js.map