widgetdc-cortex / apps /backend /src /scripts /sync-relationships-only.ts
Kraft102's picture
Deploy from GitHub Actions 2025-12-16_03-24-50
33f2414 verified
/**
* Sync only relationships to cloud (nodes already synced)
*/
import neo4j, { Driver } from 'neo4j-driver';
import { config } from 'dotenv';
import { resolve } from 'path';
config({ path: resolve(process.cwd(), '.env.production') });
config({ path: resolve(process.cwd(), '.env') });
const BATCH_SIZE = 500;
async function main() {
const localDriver = neo4j.driver(
'bolt://localhost:7687',
neo4j.auth.basic('neo4j', 'password')
);
const cloudDriver = neo4j.driver(
process.env.NEO4J_URI || '',
neo4j.auth.basic(process.env.NEO4J_USER || 'neo4j', process.env.NEO4J_PASSWORD || '')
);
console.log('═'.repeat(60));
console.log('🔗 Syncing Relationships Only');
console.log('═'.repeat(60));
const localSession = localDriver.session();
const countResult = await localSession.run('MATCH ()-[r]->() RETURN count(r) as count');
const totalRels = countResult.records[0].get('count').toNumber();
await localSession.close();
console.log(`\nTotal relationships to sync: ${totalRels}`);
let offset = 0;
let processed = 0;
const startTime = Date.now();
while (offset < totalRels) {
const session = localDriver.session();
const result = await session.run(`
MATCH (a)-[r]->(b)
RETURN type(r) as type, properties(r) as props,
elementId(a) as startId, elementId(b) as endId
SKIP $skip LIMIT $limit
`, { skip: neo4j.int(offset), limit: neo4j.int(BATCH_SIZE) });
const rels = result.records.map(r => ({
type: r.get('type') as string,
properties: r.get('props') as Record<string, unknown>,
startId: r.get('startId') as string,
endId: r.get('endId') as string
}));
await session.close();
if (rels.length === 0) break;
// Group by type
const relsByType = new Map<string, typeof rels>();
for (const rel of rels) {
if (!relsByType.has(rel.type)) {
relsByType.set(rel.type, []);
}
relsByType.get(rel.type)!.push(rel);
}
// Import each type
for (const [relType, typeRels] of relsByType) {
const cloudSession = cloudDriver.session();
try {
const relData = typeRels.map(r => ({
startId: r.startId,
endId: r.endId,
props: r.properties
}));
await cloudSession.run(`
UNWIND $rels as relData
MATCH (a {_syncId: relData.startId})
MATCH (b {_syncId: relData.endId})
MERGE (a)-[r:${relType}]->(b)
SET r = relData.props
`, { rels: relData });
} finally {
await cloudSession.close();
}
}
processed += rels.length;
offset += BATCH_SIZE;
const elapsed = (Date.now() - startTime) / 1000;
const rate = Math.round(processed / elapsed);
const eta = Math.round((totalRels - processed) / rate);
console.log(` Progress: ${processed}/${totalRels} (${rate} rels/sec, ETA: ${eta}s)`);
}
console.log(`\n✅ Synced ${processed} relationships in ${((Date.now() - startTime) / 1000).toFixed(1)}s`);
await localDriver.close();
await cloudDriver.close();
}
main().catch(console.error);