File size: 15,712 Bytes
34367da
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
/**

 * Neo4j Database Initialization Script

 *

 * Sets up schema, constraints, indexes, and seed data for WidgeTDC

 * With robust retry logic for transient errors (deadlocks, etc.)

 */

import { config } from 'dotenv';
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import neo4j, { Driver, Session } from 'neo4j-driver';

// Load .env from backend directory (don't override existing env vars)
const __dirname = fileURLToPath(new URL('.', import.meta.url));
config({ path: resolve(__dirname, '../../.env'), override: false });

// Support both NEO4J_USER and NEO4J_USERNAME for compatibility
const NEO4J_URI = process.env.NEO4J_URI || 'bolt://localhost:7687';
const NEO4J_USERNAME = process.env.NEO4J_USER || process.env.NEO4J_USERNAME || 'neo4j';
const NEO4J_PASSWORD = process.env.NEO4J_PASSWORD || 'password';

const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 1000;

async function sleep(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
}

function isRetriableError(error: any): boolean {
    return error?.code?.includes('TransientError') ||
           error?.code?.includes('Deadlock') ||
           error?.retriable === true ||
           error?.retryable === true ||
           error?.message?.includes('deadlock') ||
           error?.message?.includes('lock');
}

async function runQueryWithRetry(

    session: Session,

    query: string,

    params: Record<string, any> = {},

    retries = MAX_RETRIES

): Promise<void> {
    for (let attempt = 1; attempt <= retries; attempt++) {
        try {
            await session.run(query, params);
            const preview = query.split('\n').find(l => l.trim())?.substring(0, 50) || query.substring(0, 50);
            console.log(`  โœ“ ...`);
            return;
        } catch (error: any) {
            // Ignore "already exists" errors for constraints/indexes
            if (error.message?.includes('already exists') || error.message?.includes('EquivalentSchemaRule')) {
                console.log(`  โญ Already exists`);
                return;
            }

            // Retry on transient errors
            if (isRetriableError(error) && attempt < retries) {
                console.log(`  โš ๏ธ  Retrying (${attempt}/${retries}) after transient error...`);
                await sleep(RETRY_DELAY_MS * attempt); // Exponential backoff
                continue;
            }

            throw error;
        }
    }
}

async function createConstraints(driver: Driver): Promise<void> {
    console.log('\n๐Ÿ“‹ Creating constraints...');

    // Run each constraint in its own session to avoid deadlocks
    const constraints = [
        `CREATE CONSTRAINT entity_id IF NOT EXISTS FOR (e:Entity) REQUIRE e.id IS UNIQUE`,
        `CREATE CONSTRAINT document_id IF NOT EXISTS FOR (d:Document) REQUIRE d.id IS UNIQUE`,
        `CREATE CONSTRAINT agent_id IF NOT EXISTS FOR (a:Agent) REQUIRE a.id IS UNIQUE`,
        `CREATE CONSTRAINT memory_id IF NOT EXISTS FOR (m:Memory) REQUIRE m.id IS UNIQUE`,
        `CREATE CONSTRAINT task_id IF NOT EXISTS FOR (t:Task) REQUIRE t.id IS UNIQUE`,
        `CREATE CONSTRAINT pattern_id IF NOT EXISTS FOR (p:Pattern) REQUIRE p.id IS UNIQUE`,
    ];

    for (const constraint of constraints) {
        const session = driver.session();
        try {
            await runQueryWithRetry(session, constraint);
        } finally {
            await session.close();
        }
        // Small delay between constraints to reduce contention
        await sleep(100);
    }
}

async function createIndexes(driver: Driver): Promise<void> {
    console.log('\n๐Ÿ“‡ Creating indexes...');

    const indexes = [
        `CREATE FULLTEXT INDEX entity_fulltext IF NOT EXISTS FOR (e:Entity) ON EACH [e.name, e.content, e.description]`,
        `CREATE FULLTEXT INDEX document_fulltext IF NOT EXISTS FOR (d:Document) ON EACH [d.title, d.content, d.summary]`,
        `CREATE FULLTEXT INDEX memory_fulltext IF NOT EXISTS FOR (m:Memory) ON EACH [m.content, m.context]`,
        `CREATE INDEX entity_type IF NOT EXISTS FOR (e:Entity) ON (e.type)`,
        `CREATE INDEX entity_created IF NOT EXISTS FOR (e:Entity) ON (e.createdAt)`,
        `CREATE INDEX document_type IF NOT EXISTS FOR (d:Document) ON (d.type)`,
        `CREATE INDEX agent_status IF NOT EXISTS FOR (a:Agent) ON (a.status)`,
        `CREATE INDEX task_status IF NOT EXISTS FOR (t:Task) ON (t.status)`,
        `CREATE INDEX memory_importance IF NOT EXISTS FOR (m:Memory) ON (m.importance)`,
    ];

    for (const index of indexes) {
        const session = driver.session();
        try {
            await runQueryWithRetry(session, index);
        } finally {
            await session.close();
        }
        await sleep(100);
    }
}

async function createSeedData(driver: Driver): Promise<void> {
    console.log('\n๐ŸŒฑ Creating seed data...');

    const now = new Date().toISOString();
    const session = driver.session();

    try {
        // System Agent
        await runQueryWithRetry(session, `

            MERGE (a:Agent:Entity {id: 'agent-system'})

            SET a.name = 'System Agent',

                a.type = 'orchestrator',

                a.status = 'active',

                a.description = 'Core system orchestrator agent',

                a.createdAt = $now,

                a.updatedAt = $now

        `, { now });

        // HansPedder Agent
        await runQueryWithRetry(session, `

            MERGE (a:Agent:Entity {id: 'agent-hanspedder'})

            SET a.name = 'HansPedder',

                a.type = 'qa-tester',

                a.status = 'active',

                a.description = 'Autonomous QA testing agent',

                a.capabilities = ['testing', 'validation', 'reporting'],

                a.createdAt = $now,

                a.updatedAt = $now

        `, { now });

        // GraphRAG Agent
        await runQueryWithRetry(session, `

            MERGE (a:Agent:Entity {id: 'agent-graphrag'})

            SET a.name = 'GraphRAG Engine',

                a.type = 'retrieval',

                a.status = 'active',

                a.description = 'Graph-enhanced retrieval augmented generation',

                a.capabilities = ['search', 'retrieval', 'synthesis'],

                a.createdAt = $now,

                a.updatedAt = $now

        `, { now });

        // WidgeTDC Organization
        await runQueryWithRetry(session, `

            MERGE (o:Organization:Entity {id: 'org-widgetdc'})

            SET o.name = 'WidgeTDC',

                o.type = 'Organization',

                o.description = 'Enterprise Intelligence Platform',

                o.createdAt = $now,

                o.updatedAt = $now

        `, { now });

        // Knowledge Domains
        const domains = [
            { id: 'domain-security', name: 'Security', description: 'Cybersecurity and threat intelligence' },
            { id: 'domain-compliance', name: 'Compliance', description: 'GDPR, regulatory compliance' },
            { id: 'domain-analytics', name: 'Analytics', description: 'Data analytics and insights' },
            { id: 'domain-agents', name: 'Agents', description: 'Autonomous agent coordination' }
        ];

        for (const domain of domains) {
            await runQueryWithRetry(session, `

                MERGE (d:Domain:Entity {id: $id})

                SET d.name = $name,

                    d.type = 'Domain',

                    d.description = $description,

                    d.createdAt = $now,

                    d.updatedAt = $now

            `, { ...domain, now });
        }

        // Sample Documents
        await runQueryWithRetry(session, `

            MERGE (d:Document:Entity {id: 'doc-system-architecture'})

            SET d.title = 'WidgeTDC System Architecture',

                d.type = 'documentation',

                d.content = 'WidgeTDC is an enterprise-grade autonomous intelligence platform built as a TypeScript monorepo.',

                d.summary = 'Core system architecture documentation',

                d.source = 'internal',

                d.createdAt = $now,

                d.updatedAt = $now

        `, { now });

        await runQueryWithRetry(session, `

            MERGE (d:Document:Entity {id: 'doc-mcp-protocol'})

            SET d.title = 'MCP Protocol Integration',

                d.type = 'documentation',

                d.content = 'Model Context Protocol enables AI agents to communicate with external tools and data sources.',

                d.summary = 'MCP protocol documentation',

                d.source = 'internal',

                d.createdAt = $now,

                d.updatedAt = $now

        `, { now });

        // Sample Task
        await runQueryWithRetry(session, `

            MERGE (t:Task:Entity {id: 'task-init-neo4j'})

            SET t.name = 'Initialize Neo4j Database',

                t.type = 'setup',

                t.status = 'completed',

                t.description = 'Set up Neo4j schema, constraints, and seed data',

                t.createdAt = $now,

                t.completedAt = $now

        `, { now });

        // Sample Pattern
        await runQueryWithRetry(session, `

            MERGE (p:Pattern:Entity {id: 'pattern-graph-query'})

            SET p.name = 'Graph Query Pattern',

                p.type = 'query',

                p.description = 'Common pattern for querying knowledge graph',

                p.frequency = 0,

                p.successRate = 1.0,

                p.createdAt = $now,

                p.updatedAt = $now

        `, { now });

        // Sample Memory
        await runQueryWithRetry(session, `

            MERGE (m:Memory:Entity {id: 'memory-system-init'})

            SET m.content = 'System initialized successfully with Neo4j knowledge graph',

                m.context = 'system-startup',

                m.importance = 0.8,

                m.type = 'episodic',

                m.createdAt = $now

        `, { now });
    } finally {
        await session.close();
    }
}

async function createRelationships(driver: Driver): Promise<void> {
    console.log('\n๐Ÿ”— Creating relationships...');

    const session = driver.session();

    try {
        // Agents belong to organization
        await runQueryWithRetry(session, `

            MATCH (a:Agent), (o:Organization {id: 'org-widgetdc'})

            WHERE a.id IN ['agent-system', 'agent-hanspedder', 'agent-graphrag']

            MERGE (a)-[:BELONGS_TO]->(o)

        `);

        // Agents manage domains
        await runQueryWithRetry(session, `

            MATCH (a:Agent {id: 'agent-hanspedder'}), (d:Domain {id: 'domain-agents'})

            MERGE (a)-[:MANAGES]->(d)

        `);

        await runQueryWithRetry(session, `

            MATCH (a:Agent {id: 'agent-graphrag'}), (d:Domain {id: 'domain-analytics'})

            MERGE (a)-[:MANAGES]->(d)

        `);

        // Documents relate to domains
        await runQueryWithRetry(session, `

            MATCH (doc:Document {id: 'doc-system-architecture'}), (d:Domain {id: 'domain-analytics'})

            MERGE (doc)-[:RELATES_TO]->(d)

        `);

        await runQueryWithRetry(session, `

            MATCH (doc:Document {id: 'doc-mcp-protocol'}), (d:Domain {id: 'domain-agents'})

            MERGE (doc)-[:RELATES_TO]->(d)

        `);

        // System agent created task
        await runQueryWithRetry(session, `

            MATCH (a:Agent {id: 'agent-system'}), (t:Task {id: 'task-init-neo4j'})

            MERGE (a)-[:CREATED]->(t)

        `);

        // Memory created by system
        await runQueryWithRetry(session, `

            MATCH (a:Agent {id: 'agent-system'}), (m:Memory {id: 'memory-system-init'})

            MERGE (a)-[:RECORDED]->(m)

        `);

        // Pattern used by GraphRAG
        await runQueryWithRetry(session, `

            MATCH (a:Agent {id: 'agent-graphrag'}), (p:Pattern {id: 'pattern-graph-query'})

            MERGE (a)-[:USES]->(p)

        `);
    } finally {
        await session.close();
    }
}

async function showStatistics(driver: Driver): Promise<void> {
    console.log('\n๐Ÿ“Š Database Statistics:');

    const session = driver.session();

    try {
        const nodeResult = await session.run('MATCH (n) RETURN count(n) as count');
        console.log(`  Total nodes: ${nodeResult.records[0].get('count').toNumber()}`);

        const relResult = await session.run('MATCH ()-[r]->() RETURN count(r) as count');
        console.log(`  Total relationships: ${relResult.records[0].get('count').toNumber()}`);

        const labelResult = await session.run(`

            MATCH (n)

            UNWIND labels(n) as label

            RETURN label, count(*) as count

            ORDER BY count DESC

        `);
        console.log('  Labels:');
        labelResult.records.forEach(r => {
            console.log(`    - ${r.get('label')}: ${r.get('count').toNumber()}`);
        });

        const relTypeResult = await session.run(`

            MATCH ()-[r]->()

            RETURN type(r) as type, count(*) as count

            ORDER BY count DESC

        `);
        console.log('  Relationship types:');
        relTypeResult.records.forEach(r => {
            console.log(`    - ${r.get('type')}: ${r.get('count').toNumber()}`);
        });
    } finally {
        await session.close();
    }
}

async function initializeNeo4j(): Promise<void> {
    console.log('๐Ÿš€ Neo4j Database Initialization');
    console.log('================================');
    console.log(`Connecting to: ${NEO4J_URI}`);

    const driver: Driver = neo4j.driver(
        NEO4J_URI,
        neo4j.auth.basic(NEO4J_USERNAME, NEO4J_PASSWORD),
        {
            maxConnectionLifetime: 3 * 60 * 60 * 1000,
            maxConnectionPoolSize: 10, // Reduced to prevent contention
            connectionAcquisitionTimeout: 30 * 1000,
        }
    );

    try {
        // Verify connection with retry
        for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
            try {
                await driver.verifyConnectivity();
                console.log('โœ… Connected to Neo4j');
                break;
            } catch (error) {
                if (attempt === MAX_RETRIES) throw error;
                console.log(`  โš ๏ธ  Connection attempt ${attempt}/${MAX_RETRIES} failed, retrying...`);
                await sleep(RETRY_DELAY_MS * attempt);
            }
        }

        // Run initialization steps sequentially
        await createConstraints(driver);
        await createIndexes(driver);
        await createSeedData(driver);
        await createRelationships(driver);
        await showStatistics(driver);

        console.log('\nโœ… Neo4j initialization complete!');

    } catch (error) {
        console.error('โŒ Initialization failed:', error);
        throw error;
    } finally {
        await driver.close();
    }
}

export { initializeNeo4j };

// Run if executed directly
const isMainModule = import.meta.url === `file://${process.argv[1]?.replace(/\\/g, '/')}` ||
    process.argv[1]?.includes('initNeo4j');

if (isMainModule) {
    initializeNeo4j()
        .then(() => {
            console.log('\n๐ŸŽ‰ Done!');
            process.exit(0);
        })
        .catch((error) => {
            console.error('\n๐Ÿ’ฅ Failed:', error);
            process.exit(1);
        });
}