UNPKG

web3.db-fileconnector

Version:

GraphQL System Built on web3 technologies. Web3.DB is an open database built on top of web3 technologies. It is a decentralized, open-source database that allows users to store and query data in a secure and efficient manner. The system is designed to be

397 lines (359 loc) 11.3 kB
import { findSlotsWithContext } from "../utils/helpers.js"; import { cliColors } from "../utils/cliColors.js"; import { loadAndInitPlugins } from "../utils/plugins.js"; import { EventSource } from "cross-eventsource"; import { Type, type, decode } from "codeco"; import { commitIdAsString } from "@ceramicnetwork/codecs"; import { ModelInstanceDocument } from "@ceramicnetwork/stream-model-instance"; import logger from "../logger/index.js"; /** * The indexing service class is responsible for indexing the content stored on the Ceramic node while enabling all of the plugins "installed" * on the OrbisDB instance. */ export default class IndexingService { constructor( globalCeramic, globalDatabase, ceramics, databases, hookHandler, server, is_shared ) { logger.info( cliColors.text.cyan, "🔗 Initialized indexing service.", cliColors.reset ); this.ceramic = globalCeramic; this.database = globalDatabase; this.ceramics = ceramics; this.databases = databases; this.hookHandler = hookHandler; this.server = server; this.eventSource = null; this.is_shared = is_shared; // Go through the list of all plugins used and enable them this.initializePlugins(); } // Map each plugin to the init promise and execute it async initializePlugins() { // Retrieve all plugins installed this.plugins = await loadAndInitPlugins(); // Loads all plugins installed const initPromises = this.plugins.map(async (plugin) => { this.initPlugin(plugin); }); // Wait for all the initialization promises to resolve await Promise.all(initPromises); if (this.plugins && this.plugins.length > 0) { logger.info( cliColors.text.green, "🤖 Initialized ", cliColors.reset, this.plugins.length, cliColors.text.green, " plugin(s).", cliColors.reset ); } else { logger.info( cliColors.text.yellow, "🤖 There wasn't any plugin to initialize.", cliColors.reset ); } // Trigger the generate hook which can be used to generate new streams automatically this.hookHandler.executeHook("generate"); } /** Will init one plugin */ async initPlugin(plugin) { let { HOOKS } = await plugin.init(); logger.debug( cliColors.text.cyan, "🤖 Initialized plugin: ", cliColors.reset, plugin.id, cliColors.text.cyan, "for context: ", cliColors.reset, plugin.context ); // Manage hooks declared by plugins if (HOOKS) { for (const [hook, handler] of Object.entries(HOOKS)) { this.hookHandler.addHookHandler( hook, plugin.id, plugin.uuid, plugin.context, handler ); } } } // Will subscribe to the Ceramic node using server side events async subscribe() { logger.info( cliColors.text.cyan, "👀 Subscribed to Ceramic node updates: ", cliColors.reset, this.ceramic.node ); this.eventSource = new EventSource( this.ceramic.node + "api/v0/feed/aggregation/documents" ); this.eventSource.addEventListener("message", (event) => { try { const parsedData = decode(Codec, event.data); // Perform different action based on eventType switch (parsedData.eventType) { // Discoverd a new stream case 0: console.log( cliColors.text.cyan, "👀 Discovered new stream:", cliColors.reset, parsedData.commitId.baseID?.toString() ); this.indexStream({ stream: parsedData }); break; // Updated a new stream case 1: console.log( cliColors.text.cyan, "👀 Update discovered for stream:", cliColors.reset, parsedData.commitId.baseID?.toString() ); this.indexStream({ stream: parsedData }); break; // Detect anchoring case 2: console.log( cliColors.text.cyan, "👀 Detected anchoring for stream:", cliColors.reset, parsedData.commitId.baseID?.toString() ); break; } } catch (e) { logger.error( cliColors.text.red, "Error parsing the Ceramic event:", e, cliColors.reset ); } }); this.eventSource.addEventListener("error", (error) => { logger.error( cliColors.text.red, "🛑 Error received from Ceramic node (double check your settings and that your Ceramic node is alive): ", cliColors.reset, error ); }); } // Triggered after a new plugin install async restartPlugins(pluginToReset) { logger.info( cliColors.text.cyan, "🤖 Resetting all plugins.", cliColors.reset ); // Loop through all plugins instances and stop them this.plugins.map(async (plugin) => { /** Stop plugins */ if (plugin?.stop) { try { await plugin.stop(); } catch (e) { console.log("Error stopping plugin:", e); } } /** Reset the specified plugin's settings */ if (pluginToReset && plugin?.uuid == pluginToReset && plugin?.reset) { try { await plugin.reset(); } catch (e) { console.log("Error resetting plugin:", e); } } }); // Restart plugins this.initializePlugins(); } // Implement the stop method async stop() { // Loop through all plugins instances and stop them this.plugins.map(async (plugin) => { if (plugin?.stop) { try { await plugin.stop(); } catch (e) { console.log("Error stopping plugin:", e); } } }); // Close the EventSource if it exists if (this.eventSource) { this.eventSource.close(); this.eventSource = null; logger.debug( cliColors.text.cyan, "🛑 Unsubscribed from Ceramic node updates.", cliColors.reset ); } // Might add additional cleanup logic (if necessary) for example, releasing database connections, resetting internal state, etc. console.log( cliColors.text.green, "Indexing service stopped successfully.", cliColors.reset ); } async requestStreamIndexing(stream_id, slots = []) { const stream = await ModelInstanceDocument.load( this.ceramic.client, stream_id ); return this.indexStream({ stream }, slots); } /** Will parse the stream details, process the plugins and save the stream in DB */ async indexStream({ stream }, requestingSlots = []) { if (!stream) { logger.error( cliColors.text.red, "Error indexing a new stream:", cliColors.reset, " stream details are needed to index a stream." ); return; } // Trying to retrieve the StreamID from CommitID let streamId; try { streamId = stream.commitId.baseID?.toString(); } catch (e) { return logger.error( cliColors.text.red, "Error retrieving the StreamID for this stream:", cliColors.reset, e ); } try { // Get content let content = typeof stream.content === "object" ? stream.content : JSON.parse(stream.content); // Get model let model = stream.metadata.model; // Get context let context = stream.metadata.context; // Get controller let controller = stream.metadata.controller ? stream.metadata.controller : stream.metadata.controllers[0]; // Data ingested by the plugin let processedData = { stream_id: streamId, model: model, controller: controller, content: content, }; if (stream) { // Will execute all of the validator hooks and terminate if one of them reject the stream const { isValid } = await this.hookHandler.executeHook( "validate", processedData, context ); if (isValid == false) { return logger.debug( cliColors.text.red, "❌ Stream is not valid, don't index:", cliColors.reset, streamId ); } else { // Will execute all of the "metadata" plugins and return a pluginsData object which will contain all of the metadata added by the different plugins const { pluginsData } = await this.hookHandler.executeHook( "add_metadata", processedData, context ); // Add additional fields to the content which will be saved in the database let insertedContent = { stream_id: streamId, controller: controller, ...content, _metadata_context: context, }; // Perform insert or update based on event type // Save the stream content and indexing data in the specified database if (this.is_shared) { let slots = Array.from( new Set([ ...requestingSlots, ...(findSlotsWithContext(context) || []), ]) ); logger.debug("slots:", slots); // Insert in each slot using this context for (const slot of slots) { if (this.databases[slot]) { await this.databases[slot].upsert( model, insertedContent, pluginsData ); } else { logger.error(`Upsert method not found for slot: ${slot}`); } } } else { await this.database.upsert(model, insertedContent, pluginsData); } // Will execute all of the post-processor plugins. this.hookHandler.executeHook( "post_process", { ...insertedContent, model, pluginsData, }, context ); } } } catch (e) { logger.error( cliColors.text.red, "Error indexing stream:", cliColors.reset, e ); } } } export const JsonAsString = new Type( "JSON-as-string", (_input) => true, (input, context) => { try { return context.success(JSON.parse(input)); } catch { return context.failure(); } }, (commitID) => JSON.stringify(commitID) ); export const AggregationDocument = type({ commitId: commitIdAsString, }); //# sourceMappingURL=feed.js.map const Codec = JsonAsString.pipe(AggregationDocument);