mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 13:33:11 +00:00
chore: add pre-built dist folder for npx usage
This commit is contained in:
committed by
Romuald Członkowski
parent
a70d96a373
commit
5057481e70
518
dist/services/node-documentation-service.js
vendored
Normal file
518
dist/services/node-documentation-service.js
vendored
Normal file
@@ -0,0 +1,518 @@
|
||||
"use strict";
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NodeDocumentationService = void 0;
|
||||
const crypto_1 = require("crypto");
|
||||
const path_1 = __importDefault(require("path"));
|
||||
const logger_1 = require("../utils/logger");
|
||||
const node_source_extractor_1 = require("../utils/node-source-extractor");
|
||||
const enhanced_documentation_fetcher_1 = require("../utils/enhanced-documentation-fetcher");
|
||||
const example_generator_1 = require("../utils/example-generator");
|
||||
const database_adapter_1 = require("../database/database-adapter");
|
||||
class NodeDocumentationService {
|
||||
constructor(dbPath) {
|
||||
this.db = null;
|
||||
this.dbPath = dbPath || process.env.NODE_DB_PATH || this.findDatabasePath();
|
||||
const dbDir = path_1.default.dirname(this.dbPath);
|
||||
if (!require('fs').existsSync(dbDir)) {
|
||||
require('fs').mkdirSync(dbDir, { recursive: true });
|
||||
}
|
||||
this.extractor = new node_source_extractor_1.NodeSourceExtractor();
|
||||
this.docsFetcher = new enhanced_documentation_fetcher_1.EnhancedDocumentationFetcher();
|
||||
this.initialized = this.initializeAsync();
|
||||
}
|
||||
findDatabasePath() {
|
||||
const fs = require('fs');
|
||||
const localPath = path_1.default.join(process.cwd(), 'data', 'nodes.db');
|
||||
if (fs.existsSync(localPath)) {
|
||||
return localPath;
|
||||
}
|
||||
const packagePath = path_1.default.join(__dirname, '..', '..', 'data', 'nodes.db');
|
||||
if (fs.existsSync(packagePath)) {
|
||||
return packagePath;
|
||||
}
|
||||
const globalPath = path_1.default.join(__dirname, '..', '..', '..', 'data', 'nodes.db');
|
||||
if (fs.existsSync(globalPath)) {
|
||||
return globalPath;
|
||||
}
|
||||
return localPath;
|
||||
}
|
||||
async initializeAsync() {
|
||||
try {
|
||||
this.db = await (0, database_adapter_1.createDatabaseAdapter)(this.dbPath);
|
||||
this.initializeDatabase();
|
||||
logger_1.logger.info('Node Documentation Service initialized');
|
||||
}
|
||||
catch (error) {
|
||||
logger_1.logger.error('Failed to initialize database adapter', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async ensureInitialized() {
|
||||
await this.initialized;
|
||||
if (!this.db) {
|
||||
throw new Error('Database not initialized');
|
||||
}
|
||||
}
|
||||
initializeDatabase() {
|
||||
if (!this.db)
|
||||
throw new Error('Database not initialized');
|
||||
const schema = `
|
||||
-- Main nodes table with documentation and examples
|
||||
CREATE TABLE IF NOT EXISTS nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
node_type TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
display_name TEXT,
|
||||
description TEXT,
|
||||
category TEXT,
|
||||
subcategory TEXT,
|
||||
icon TEXT,
|
||||
|
||||
-- Source code
|
||||
source_code TEXT NOT NULL,
|
||||
credential_code TEXT,
|
||||
code_hash TEXT NOT NULL,
|
||||
code_length INTEGER NOT NULL,
|
||||
|
||||
-- Documentation
|
||||
documentation_markdown TEXT,
|
||||
documentation_url TEXT,
|
||||
documentation_title TEXT,
|
||||
|
||||
-- Enhanced documentation fields (stored as JSON)
|
||||
operations TEXT,
|
||||
api_methods TEXT,
|
||||
documentation_examples TEXT,
|
||||
templates TEXT,
|
||||
related_resources TEXT,
|
||||
required_scopes TEXT,
|
||||
|
||||
-- Example usage
|
||||
example_workflow TEXT,
|
||||
example_parameters TEXT,
|
||||
properties_schema TEXT,
|
||||
|
||||
-- Metadata
|
||||
package_name TEXT NOT NULL,
|
||||
version TEXT,
|
||||
codex_data TEXT,
|
||||
aliases TEXT,
|
||||
|
||||
-- Flags
|
||||
has_credentials INTEGER DEFAULT 0,
|
||||
is_trigger INTEGER DEFAULT 0,
|
||||
is_webhook INTEGER DEFAULT 0,
|
||||
|
||||
-- Timestamps
|
||||
extracted_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Indexes
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_package_name ON nodes(package_name);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_category ON nodes(category);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_code_hash ON nodes(code_hash);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_name ON nodes(name);
|
||||
CREATE INDEX IF NOT EXISTS idx_nodes_is_trigger ON nodes(is_trigger);
|
||||
|
||||
-- Full Text Search
|
||||
CREATE VIRTUAL TABLE IF NOT EXISTS nodes_fts USING fts5(
|
||||
node_type,
|
||||
name,
|
||||
display_name,
|
||||
description,
|
||||
category,
|
||||
documentation_markdown,
|
||||
aliases,
|
||||
content=nodes,
|
||||
content_rowid=id
|
||||
);
|
||||
|
||||
-- Triggers for FTS
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_ai AFTER INSERT ON nodes
|
||||
BEGIN
|
||||
INSERT INTO nodes_fts(rowid, node_type, name, display_name, description, category, documentation_markdown, aliases)
|
||||
VALUES (new.id, new.node_type, new.name, new.display_name, new.description, new.category, new.documentation_markdown, new.aliases);
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_ad AFTER DELETE ON nodes
|
||||
BEGIN
|
||||
DELETE FROM nodes_fts WHERE rowid = old.id;
|
||||
END;
|
||||
|
||||
CREATE TRIGGER IF NOT EXISTS nodes_au AFTER UPDATE ON nodes
|
||||
BEGIN
|
||||
DELETE FROM nodes_fts WHERE rowid = old.id;
|
||||
INSERT INTO nodes_fts(rowid, node_type, name, display_name, description, category, documentation_markdown, aliases)
|
||||
VALUES (new.id, new.node_type, new.name, new.display_name, new.description, new.category, new.documentation_markdown, new.aliases);
|
||||
END;
|
||||
|
||||
-- Documentation sources table
|
||||
CREATE TABLE IF NOT EXISTS documentation_sources (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
source TEXT NOT NULL,
|
||||
commit_hash TEXT,
|
||||
fetched_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- Statistics table
|
||||
CREATE TABLE IF NOT EXISTS extraction_stats (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
total_nodes INTEGER NOT NULL,
|
||||
nodes_with_docs INTEGER NOT NULL,
|
||||
nodes_with_examples INTEGER NOT NULL,
|
||||
total_code_size INTEGER NOT NULL,
|
||||
total_docs_size INTEGER NOT NULL,
|
||||
extraction_date DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
`;
|
||||
this.db.exec(schema);
|
||||
}
|
||||
async storeNode(nodeInfo) {
|
||||
await this.ensureInitialized();
|
||||
const hash = this.generateHash(nodeInfo.sourceCode);
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT OR REPLACE INTO nodes (
|
||||
node_type, name, display_name, description, category, subcategory, icon,
|
||||
source_code, credential_code, code_hash, code_length,
|
||||
documentation_markdown, documentation_url, documentation_title,
|
||||
operations, api_methods, documentation_examples, templates, related_resources, required_scopes,
|
||||
example_workflow, example_parameters, properties_schema,
|
||||
package_name, version, codex_data, aliases,
|
||||
has_credentials, is_trigger, is_webhook
|
||||
) VALUES (
|
||||
@nodeType, @name, @displayName, @description, @category, @subcategory, @icon,
|
||||
@sourceCode, @credentialCode, @hash, @codeLength,
|
||||
@documentation, @documentationUrl, @documentationTitle,
|
||||
@operations, @apiMethods, @documentationExamples, @templates, @relatedResources, @requiredScopes,
|
||||
@exampleWorkflow, @exampleParameters, @propertiesSchema,
|
||||
@packageName, @version, @codexData, @aliases,
|
||||
@hasCredentials, @isTrigger, @isWebhook
|
||||
)
|
||||
`);
|
||||
stmt.run({
|
||||
nodeType: nodeInfo.nodeType,
|
||||
name: nodeInfo.name,
|
||||
displayName: nodeInfo.displayName || nodeInfo.name,
|
||||
description: nodeInfo.description || '',
|
||||
category: nodeInfo.category || 'Other',
|
||||
subcategory: nodeInfo.subcategory || null,
|
||||
icon: nodeInfo.icon || null,
|
||||
sourceCode: nodeInfo.sourceCode,
|
||||
credentialCode: nodeInfo.credentialCode || null,
|
||||
hash,
|
||||
codeLength: nodeInfo.sourceCode.length,
|
||||
documentation: nodeInfo.documentationMarkdown || null,
|
||||
documentationUrl: nodeInfo.documentationUrl || null,
|
||||
documentationTitle: nodeInfo.documentationTitle || null,
|
||||
operations: nodeInfo.operations ? JSON.stringify(nodeInfo.operations) : null,
|
||||
apiMethods: nodeInfo.apiMethods ? JSON.stringify(nodeInfo.apiMethods) : null,
|
||||
documentationExamples: nodeInfo.documentationExamples ? JSON.stringify(nodeInfo.documentationExamples) : null,
|
||||
templates: nodeInfo.templates ? JSON.stringify(nodeInfo.templates) : null,
|
||||
relatedResources: nodeInfo.relatedResources ? JSON.stringify(nodeInfo.relatedResources) : null,
|
||||
requiredScopes: nodeInfo.requiredScopes ? JSON.stringify(nodeInfo.requiredScopes) : null,
|
||||
exampleWorkflow: nodeInfo.exampleWorkflow ? JSON.stringify(nodeInfo.exampleWorkflow) : null,
|
||||
exampleParameters: nodeInfo.exampleParameters ? JSON.stringify(nodeInfo.exampleParameters) : null,
|
||||
propertiesSchema: nodeInfo.propertiesSchema ? JSON.stringify(nodeInfo.propertiesSchema) : null,
|
||||
packageName: nodeInfo.packageName,
|
||||
version: nodeInfo.version || null,
|
||||
codexData: nodeInfo.codexData ? JSON.stringify(nodeInfo.codexData) : null,
|
||||
aliases: nodeInfo.aliases ? JSON.stringify(nodeInfo.aliases) : null,
|
||||
hasCredentials: nodeInfo.hasCredentials ? 1 : 0,
|
||||
isTrigger: nodeInfo.isTrigger ? 1 : 0,
|
||||
isWebhook: nodeInfo.isWebhook ? 1 : 0
|
||||
});
|
||||
}
|
||||
async getNodeInfo(nodeType) {
|
||||
await this.ensureInitialized();
|
||||
const stmt = this.db.prepare(`
|
||||
SELECT * FROM nodes WHERE node_type = ? OR name = ? COLLATE NOCASE
|
||||
`);
|
||||
const row = stmt.get(nodeType, nodeType);
|
||||
if (!row)
|
||||
return null;
|
||||
return this.rowToNodeInfo(row);
|
||||
}
|
||||
async searchNodes(options) {
|
||||
await this.ensureInitialized();
|
||||
let query = 'SELECT * FROM nodes WHERE 1=1';
|
||||
const params = {};
|
||||
if (options.query) {
|
||||
query += ` AND id IN (
|
||||
SELECT rowid FROM nodes_fts
|
||||
WHERE nodes_fts MATCH @query
|
||||
)`;
|
||||
params.query = options.query;
|
||||
}
|
||||
if (options.nodeType) {
|
||||
query += ' AND node_type LIKE @nodeType';
|
||||
params.nodeType = `%${options.nodeType}%`;
|
||||
}
|
||||
if (options.packageName) {
|
||||
query += ' AND package_name = @packageName';
|
||||
params.packageName = options.packageName;
|
||||
}
|
||||
if (options.category) {
|
||||
query += ' AND category = @category';
|
||||
params.category = options.category;
|
||||
}
|
||||
if (options.hasCredentials !== undefined) {
|
||||
query += ' AND has_credentials = @hasCredentials';
|
||||
params.hasCredentials = options.hasCredentials ? 1 : 0;
|
||||
}
|
||||
if (options.isTrigger !== undefined) {
|
||||
query += ' AND is_trigger = @isTrigger';
|
||||
params.isTrigger = options.isTrigger ? 1 : 0;
|
||||
}
|
||||
query += ' ORDER BY name LIMIT @limit';
|
||||
params.limit = options.limit || 20;
|
||||
const stmt = this.db.prepare(query);
|
||||
const rows = stmt.all(params);
|
||||
return rows.map(row => this.rowToNodeInfo(row));
|
||||
}
|
||||
async listNodes() {
|
||||
await this.ensureInitialized();
|
||||
const stmt = this.db.prepare('SELECT * FROM nodes ORDER BY name');
|
||||
const rows = stmt.all();
|
||||
return rows.map(row => this.rowToNodeInfo(row));
|
||||
}
|
||||
async rebuildDatabase() {
|
||||
await this.ensureInitialized();
|
||||
logger_1.logger.info('Starting complete database rebuild...');
|
||||
this.db.exec('DELETE FROM nodes');
|
||||
this.db.exec('DELETE FROM extraction_stats');
|
||||
await this.docsFetcher.ensureDocsRepository();
|
||||
const stats = {
|
||||
total: 0,
|
||||
successful: 0,
|
||||
failed: 0,
|
||||
errors: []
|
||||
};
|
||||
try {
|
||||
const availableNodes = await this.extractor.listAvailableNodes();
|
||||
stats.total = availableNodes.length;
|
||||
logger_1.logger.info(`Found ${stats.total} nodes to process`);
|
||||
const batchSize = 10;
|
||||
for (let i = 0; i < availableNodes.length; i += batchSize) {
|
||||
const batch = availableNodes.slice(i, i + batchSize);
|
||||
await Promise.all(batch.map(async (node) => {
|
||||
try {
|
||||
const nodeType = `n8n-nodes-base.${node.name}`;
|
||||
const nodeData = await this.extractor.extractNodeSource(nodeType);
|
||||
if (!nodeData || !nodeData.sourceCode) {
|
||||
throw new Error('Failed to extract node source');
|
||||
}
|
||||
const nodeDefinition = this.parseNodeDefinition(nodeData.sourceCode);
|
||||
const enhancedDocs = await this.docsFetcher.getEnhancedNodeDocumentation(nodeType);
|
||||
const example = example_generator_1.ExampleGenerator.generateFromNodeDefinition(nodeDefinition);
|
||||
const nodeInfo = {
|
||||
nodeType: nodeType,
|
||||
name: node.name,
|
||||
displayName: nodeDefinition.displayName || node.displayName || node.name,
|
||||
description: nodeDefinition.description || node.description || '',
|
||||
category: nodeDefinition.category || 'Other',
|
||||
subcategory: nodeDefinition.subcategory,
|
||||
icon: nodeDefinition.icon,
|
||||
sourceCode: nodeData.sourceCode,
|
||||
credentialCode: nodeData.credentialCode,
|
||||
documentationMarkdown: enhancedDocs?.markdown,
|
||||
documentationUrl: enhancedDocs?.url,
|
||||
documentationTitle: enhancedDocs?.title,
|
||||
operations: enhancedDocs?.operations,
|
||||
apiMethods: enhancedDocs?.apiMethods,
|
||||
documentationExamples: enhancedDocs?.examples,
|
||||
templates: enhancedDocs?.templates,
|
||||
relatedResources: enhancedDocs?.relatedResources,
|
||||
requiredScopes: enhancedDocs?.requiredScopes,
|
||||
exampleWorkflow: example,
|
||||
exampleParameters: example.nodes[0]?.parameters,
|
||||
propertiesSchema: nodeDefinition.properties,
|
||||
packageName: nodeData.packageInfo?.name || 'n8n-nodes-base',
|
||||
version: nodeDefinition.version,
|
||||
codexData: nodeDefinition.codex,
|
||||
aliases: nodeDefinition.alias,
|
||||
hasCredentials: !!nodeData.credentialCode,
|
||||
isTrigger: node.name.toLowerCase().includes('trigger'),
|
||||
isWebhook: node.name.toLowerCase().includes('webhook')
|
||||
};
|
||||
await this.storeNode(nodeInfo);
|
||||
stats.successful++;
|
||||
logger_1.logger.debug(`Processed node: ${nodeType}`);
|
||||
}
|
||||
catch (error) {
|
||||
stats.failed++;
|
||||
const errorMsg = `Failed to process ${node.name}: ${error instanceof Error ? error.message : String(error)}`;
|
||||
stats.errors.push(errorMsg);
|
||||
logger_1.logger.error(errorMsg);
|
||||
}
|
||||
}));
|
||||
logger_1.logger.info(`Progress: ${Math.min(i + batchSize, availableNodes.length)}/${stats.total} nodes processed`);
|
||||
}
|
||||
this.storeStatistics(stats);
|
||||
logger_1.logger.info(`Database rebuild complete: ${stats.successful} successful, ${stats.failed} failed`);
|
||||
}
|
||||
catch (error) {
|
||||
logger_1.logger.error('Database rebuild failed:', error);
|
||||
throw error;
|
||||
}
|
||||
return stats;
|
||||
}
|
||||
parseNodeDefinition(sourceCode) {
|
||||
const result = {
|
||||
displayName: '',
|
||||
description: '',
|
||||
properties: [],
|
||||
category: null,
|
||||
subcategory: null,
|
||||
icon: null,
|
||||
version: null,
|
||||
codex: null,
|
||||
alias: null
|
||||
};
|
||||
try {
|
||||
const displayNameMatch = sourceCode.match(/displayName\s*[:=]\s*['"`]([^'"`]+)['"`]/);
|
||||
if (displayNameMatch) {
|
||||
result.displayName = displayNameMatch[1];
|
||||
}
|
||||
const descriptionMatch = sourceCode.match(/description\s*[:=]\s*['"`]([^'"`]+)['"`]/);
|
||||
if (descriptionMatch) {
|
||||
result.description = descriptionMatch[1];
|
||||
}
|
||||
const iconMatch = sourceCode.match(/icon\s*[:=]\s*['"`]([^'"`]+)['"`]/);
|
||||
if (iconMatch) {
|
||||
result.icon = iconMatch[1];
|
||||
}
|
||||
const groupMatch = sourceCode.match(/group\s*[:=]\s*\[['"`]([^'"`]+)['"`]\]/);
|
||||
if (groupMatch) {
|
||||
result.category = groupMatch[1];
|
||||
}
|
||||
const versionMatch = sourceCode.match(/version\s*[:=]\s*(\d+)/);
|
||||
if (versionMatch) {
|
||||
result.version = parseInt(versionMatch[1]);
|
||||
}
|
||||
const subtitleMatch = sourceCode.match(/subtitle\s*[:=]\s*['"`]([^'"`]+)['"`]/);
|
||||
if (subtitleMatch) {
|
||||
result.subtitle = subtitleMatch[1];
|
||||
}
|
||||
const propsMatch = sourceCode.match(/properties\s*[:=]\s*(\[[\s\S]*?\])\s*[,}]/);
|
||||
if (propsMatch) {
|
||||
try {
|
||||
result.properties = [];
|
||||
}
|
||||
catch (e) {
|
||||
}
|
||||
}
|
||||
if (sourceCode.includes('implements.*ITrigger') ||
|
||||
sourceCode.includes('polling:.*true') ||
|
||||
sourceCode.includes('webhook:.*true') ||
|
||||
result.displayName.toLowerCase().includes('trigger')) {
|
||||
result.isTrigger = true;
|
||||
}
|
||||
if (sourceCode.includes('webhooks:') ||
|
||||
sourceCode.includes('webhook:.*true') ||
|
||||
result.displayName.toLowerCase().includes('webhook')) {
|
||||
result.isWebhook = true;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
logger_1.logger.debug('Error parsing node definition:', error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
rowToNodeInfo(row) {
|
||||
return {
|
||||
nodeType: row.node_type,
|
||||
name: row.name,
|
||||
displayName: row.display_name,
|
||||
description: row.description,
|
||||
category: row.category,
|
||||
subcategory: row.subcategory,
|
||||
icon: row.icon,
|
||||
sourceCode: row.source_code,
|
||||
credentialCode: row.credential_code,
|
||||
documentationMarkdown: row.documentation_markdown,
|
||||
documentationUrl: row.documentation_url,
|
||||
documentationTitle: row.documentation_title,
|
||||
operations: row.operations ? JSON.parse(row.operations) : null,
|
||||
apiMethods: row.api_methods ? JSON.parse(row.api_methods) : null,
|
||||
documentationExamples: row.documentation_examples ? JSON.parse(row.documentation_examples) : null,
|
||||
templates: row.templates ? JSON.parse(row.templates) : null,
|
||||
relatedResources: row.related_resources ? JSON.parse(row.related_resources) : null,
|
||||
requiredScopes: row.required_scopes ? JSON.parse(row.required_scopes) : null,
|
||||
exampleWorkflow: row.example_workflow ? JSON.parse(row.example_workflow) : null,
|
||||
exampleParameters: row.example_parameters ? JSON.parse(row.example_parameters) : null,
|
||||
propertiesSchema: row.properties_schema ? JSON.parse(row.properties_schema) : null,
|
||||
packageName: row.package_name,
|
||||
version: row.version,
|
||||
codexData: row.codex_data ? JSON.parse(row.codex_data) : null,
|
||||
aliases: row.aliases ? JSON.parse(row.aliases) : null,
|
||||
hasCredentials: row.has_credentials === 1,
|
||||
isTrigger: row.is_trigger === 1,
|
||||
isWebhook: row.is_webhook === 1
|
||||
};
|
||||
}
|
||||
generateHash(content) {
|
||||
return (0, crypto_1.createHash)('sha256').update(content).digest('hex');
|
||||
}
|
||||
storeStatistics(stats) {
|
||||
if (!this.db)
|
||||
throw new Error('Database not initialized');
|
||||
const stmt = this.db.prepare(`
|
||||
INSERT INTO extraction_stats (
|
||||
total_nodes, nodes_with_docs, nodes_with_examples,
|
||||
total_code_size, total_docs_size
|
||||
) VALUES (?, ?, ?, ?, ?)
|
||||
`);
|
||||
const sizeStats = this.db.prepare(`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN documentation_markdown IS NOT NULL THEN 1 ELSE 0 END) as with_docs,
|
||||
SUM(CASE WHEN example_workflow IS NOT NULL THEN 1 ELSE 0 END) as with_examples,
|
||||
SUM(code_length) as code_size,
|
||||
SUM(LENGTH(documentation_markdown)) as docs_size
|
||||
FROM nodes
|
||||
`).get();
|
||||
stmt.run(stats.successful, sizeStats?.with_docs || 0, sizeStats?.with_examples || 0, sizeStats?.code_size || 0, sizeStats?.docs_size || 0);
|
||||
}
|
||||
async getStatistics() {
|
||||
await this.ensureInitialized();
|
||||
const stats = this.db.prepare(`
|
||||
SELECT
|
||||
COUNT(*) as totalNodes,
|
||||
COUNT(DISTINCT package_name) as totalPackages,
|
||||
SUM(code_length) as totalCodeSize,
|
||||
SUM(CASE WHEN documentation_markdown IS NOT NULL THEN 1 ELSE 0 END) as nodesWithDocs,
|
||||
SUM(CASE WHEN example_workflow IS NOT NULL THEN 1 ELSE 0 END) as nodesWithExamples,
|
||||
SUM(has_credentials) as nodesWithCredentials,
|
||||
SUM(is_trigger) as triggerNodes,
|
||||
SUM(is_webhook) as webhookNodes
|
||||
FROM nodes
|
||||
`).get();
|
||||
const packages = this.db.prepare(`
|
||||
SELECT package_name as package, COUNT(*) as count
|
||||
FROM nodes
|
||||
GROUP BY package_name
|
||||
ORDER BY count DESC
|
||||
`).all();
|
||||
return {
|
||||
totalNodes: stats?.totalNodes || 0,
|
||||
totalPackages: stats?.totalPackages || 0,
|
||||
totalCodeSize: stats?.totalCodeSize || 0,
|
||||
nodesWithDocs: stats?.nodesWithDocs || 0,
|
||||
nodesWithExamples: stats?.nodesWithExamples || 0,
|
||||
nodesWithCredentials: stats?.nodesWithCredentials || 0,
|
||||
triggerNodes: stats?.triggerNodes || 0,
|
||||
webhookNodes: stats?.webhookNodes || 0,
|
||||
packageDistribution: packages
|
||||
};
|
||||
}
|
||||
async close() {
|
||||
await this.ensureInitialized();
|
||||
this.db.close();
|
||||
}
|
||||
}
|
||||
exports.NodeDocumentationService = NodeDocumentationService;
|
||||
//# sourceMappingURL=node-documentation-service.js.map
|
||||
Reference in New Issue
Block a user