UNPKG

jinaga

Version:

Data management for web and mobile applications.

203 lines 13.3 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.ObservableSource = void 0; const description_1 = require("../specification/description"); const encoding_1 = require("../util/encoding"); const trace_1 = require("../util/trace"); class ObservableSource { constructor(store) { this.store = store; this.listenersByTypeAndSpecification = new Map(); } notify(saved) { return __awaiter(this, void 0, void 0, function* () { // Collect all notification promises to ensure all callbacks complete const notificationPromises = []; for (let index = 0; index < saved.length; index++) { const envelope = saved[index]; notificationPromises.push(this.notifyFactSaved(envelope.fact)); } // Wait for all notifications to complete before resolving yield Promise.all(notificationPromises); }); } addSpecificationListener(specification, onResult) { if (specification.given.length !== 1) { throw new Error("Specification must have exactly one given fact"); } const givenType = specification.given[0].label.type; const givenName = specification.given[0].label.name; const specificationKey = (0, encoding_1.computeStringHash)((0, description_1.describeSpecification)(specification, 0)); const hasNestedSpecs = specification.projection.type === "composite" && specification.projection.components.some(c => c.type === "specification"); trace_1.Trace.info(`[ObservableSource] ADD_LISTENER REQUEST - Type: ${givenType}, Name: ${givenName}, Spec key: ${specificationKey.substring(0, 8)}..., Has nested specs: ${hasNestedSpecs}`); let listenersBySpecification = this.listenersByTypeAndSpecification.get(givenType); if (!listenersBySpecification) { listenersBySpecification = new Map(); this.listenersByTypeAndSpecification.set(givenType, listenersBySpecification); trace_1.Trace.info(`[ObservableSource] Created new listener map for type: ${givenType}`); } let listeners = listenersBySpecification.get(specificationKey); if (!listeners) { listeners = { specification, listeners: [] }; listenersBySpecification.set(specificationKey, listeners); trace_1.Trace.info(`[ObservableSource] Created new listener group for spec: ${specificationKey.substring(0, 8)}... (type: ${givenType})`); } const specificationListener = { onResult }; listeners.listeners.push(specificationListener); const listenerCount = listeners.listeners.length; const totalListeners = Array.from(this.listenersByTypeAndSpecification.values()) .reduce((total, map) => total + Array.from(map.values()).reduce((sum, l) => sum + l.listeners.length, 0), 0); trace_1.Trace.info(`[ObservableSource] LISTENER ADDED - Spec: ${specificationKey.substring(0, 8)}..., Type: ${givenType}, Count for spec: ${listenerCount}, Total listeners: ${totalListeners}, Nested specs: ${hasNestedSpecs}`); return specificationListener; } removeSpecificationListener(specificationListener) { const startTime = Date.now(); let found = false; let removedFromSpec = ''; let removedFromType = ''; for (const [givenType, listenersBySpecification] of this.listenersByTypeAndSpecification) { for (const [specificationKey, listeners] of listenersBySpecification) { const beforeCount = listeners.listeners.length; const index = listeners.listeners.indexOf(specificationListener); if (index >= 0) { trace_1.Trace.info(`[ObservableSource] REMOVING listener - Spec: ${specificationKey.substring(0, 8)}..., Type: ${givenType}, Index: ${index}, Before count: ${beforeCount}`); listeners.listeners.splice(index, 1); found = true; removedFromSpec = specificationKey; removedFromType = givenType; const afterCount = listeners.listeners.length; trace_1.Trace.info(`[ObservableSource] REMOVED listener - After count: ${afterCount}`); if (listeners.listeners.length === 0) { listenersBySpecification.delete(specificationKey); trace_1.Trace.info(`[ObservableSource] Deleted empty spec group: ${specificationKey.substring(0, 8)}...`); if (listenersBySpecification.size === 0) { this.listenersByTypeAndSpecification.delete(givenType); trace_1.Trace.info(`[ObservableSource] Deleted empty type group: ${givenType}`); } } break; } } if (found) break; } const totalListeners = Array.from(this.listenersByTypeAndSpecification.values()) .reduce((total, map) => total + Array.from(map.values()).reduce((sum, l) => sum + l.listeners.length, 0), 0); const duration = Date.now() - startTime; if (found) { trace_1.Trace.info(`[ObservableSource] Listener removal completed - Spec: ${removedFromSpec.substring(0, 8)}..., Type: ${removedFromType}, Total remaining: ${totalListeners}, Duration: ${duration}ms`); } else { trace_1.Trace.warn(`[ObservableSource] Listener NOT FOUND during removal - Total listeners: ${totalListeners}, Duration: ${duration}ms`); } } notifyFactSaved(fact) { return __awaiter(this, void 0, void 0, function* () { const startTime = Date.now(); const listenersBySpecification = this.listenersByTypeAndSpecification.get(fact.type); if (listenersBySpecification) { trace_1.Trace.info(`[ObservableSource] NOTIFY START - Fact type: ${fact.type}, Hash: ${fact.hash.substring(0, 8)}..., Spec groups: ${listenersBySpecification.size}`); let totalNotifications = 0; let specCount = 0; let nestedSpecCount = 0; for (const [specificationKey, listeners] of listenersBySpecification) { specCount++; if (listeners && listeners.listeners.length > 0) { const listenerCount = listeners.listeners.length; const specification = listeners.specification; const hasNestedSpecs = specification.projection.type === "composite" && specification.projection.components.some(c => c.type === "specification"); if (hasNestedSpecs) { nestedSpecCount++; const nestedSpecNames = specification.projection.type === "composite" ? specification.projection.components .filter(c => c.type === "specification") .map(c => c.name) .join(', ') : ''; trace_1.Trace.info(`[ObservableSource] NESTED SPEC DETECTED - Spec ${specCount}/${listenersBySpecification.size}, Key: ${specificationKey.substring(0, 8)}..., Nested components: [${nestedSpecNames}], Listeners: ${listenerCount}`); } else { trace_1.Trace.info(`[ObservableSource] Processing spec ${specCount}/${listenersBySpecification.size} - Key: ${specificationKey.substring(0, 8)}..., Listeners: ${listenerCount}`); } const givenReference = { type: fact.type, hash: fact.hash }; const readStart = Date.now(); const results = yield this.store.read([givenReference], specification); const readDuration = Date.now() - readStart; if (hasNestedSpecs) { trace_1.Trace.info(`[ObservableSource] Store read for NESTED spec - Results: ${results.length}, Duration: ${readDuration}ms`); // Log nested result structure if present if (results.length > 0 && specification.projection.type === "composite") { const nestedResults = specification.projection.components .filter(c => c.type === "specification") .map(c => { var _a; return `${c.name}: ${((_a = results[0].result[c.name]) === null || _a === void 0 ? void 0 : _a.length) || 0}`; }) .join(', '); trace_1.Trace.info(`[ObservableSource] Nested results structure: {${nestedResults}}`); } } else { trace_1.Trace.info(`[ObservableSource] Store read completed - Results: ${results.length}, Duration: ${readDuration}ms`); } // Create a snapshot of listeners to avoid modification during iteration const listenerSnapshot = [...listeners.listeners]; if (listenerSnapshot.length !== listeners.listeners.length) { trace_1.Trace.warn(`[ObservableSource] RACE CONDITION DETECTED - Listener count changed during snapshot: ${listenerSnapshot.length} vs ${listeners.listeners.length}`); } for (let i = 0; i < listenerSnapshot.length; i++) { const specificationListener = listenerSnapshot[i]; if (specificationListener) { try { const notifyStart = Date.now(); trace_1.Trace.info(`[ObservableSource] Calling listener ${i + 1}/${listenerSnapshot.length} - Nested: ${hasNestedSpecs}`); yield specificationListener.onResult(results); const notifyDuration = Date.now() - notifyStart; totalNotifications++; if (notifyDuration > 100) { trace_1.Trace.warn(`[ObservableSource] SLOW notification - Listener ${i + 1}/${listenerSnapshot.length}, Duration: ${notifyDuration}ms, Nested: ${hasNestedSpecs}`); } else { trace_1.Trace.info(`[ObservableSource] Listener completed - ${i + 1}/${listenerSnapshot.length}, Duration: ${notifyDuration}ms`); } } catch (error) { trace_1.Trace.error(`[ObservableSource] ERROR in listener notification - Listener ${i + 1}/${listenerSnapshot.length}, Nested: ${hasNestedSpecs}, Error: ${error}`); } } else { trace_1.Trace.warn(`[ObservableSource] NULL listener encountered at index ${i}`); } } } else { trace_1.Trace.info(`[ObservableSource] Skipping spec ${specCount}/${listenersBySpecification.size} - No listeners or null group`); } } const totalDuration = Date.now() - startTime; trace_1.Trace.info(`[ObservableSource] NOTIFY COMPLETE - Fact: ${fact.hash.substring(0, 8)}..., Type: ${fact.type}, Specs processed: ${specCount} (${nestedSpecCount} nested), Total notifications: ${totalNotifications}, Duration: ${totalDuration}ms`); } else { trace_1.Trace.info(`[ObservableSource] No listeners for fact type: ${fact.type} - Available types: [${Array.from(this.listenersByTypeAndSpecification.keys()).join(', ')}]`); } }); } } exports.ObservableSource = ObservableSource; //# sourceMappingURL=observable.js.map