mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 09:38:27 +00:00
128 lines
3.7 KiB
TypeScript
128 lines
3.7 KiB
TypeScript
import neo4j from "neo4j-driver";
|
|
import { env } from "~/env.server";
|
|
import { logger } from "~/services/logger.service";
|
|
|
|
// Create a driver instance
|
|
const driver = neo4j.driver(
|
|
env.NEO4J_URI,
|
|
neo4j.auth.basic(env.NEO4J_USERNAME, env.NEO4J_PASSWORD),
|
|
{
|
|
maxConnectionPoolSize: 50,
|
|
logging: {
|
|
level: "info",
|
|
logger: (level, message) => {
|
|
logger.info(message);
|
|
},
|
|
},
|
|
},
|
|
);
|
|
|
|
// Test the connection
|
|
const verifyConnectivity = async () => {
|
|
try {
|
|
await driver.verifyConnectivity();
|
|
logger.info("Connected to Neo4j database");
|
|
return true;
|
|
} catch (error) {
|
|
logger.error("Failed to connect to Neo4j database");
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Run a Cypher query
|
|
const runQuery = async (cypher: string, params = {}) => {
|
|
const session = driver.session();
|
|
try {
|
|
const result = await session.run(cypher, params);
|
|
return result.records;
|
|
} catch (error) {
|
|
logger.error(`Error running Cypher query: ${cypher} ${error}`);
|
|
throw error;
|
|
} finally {
|
|
await session.close();
|
|
}
|
|
};
|
|
|
|
// Initialize the database schema
|
|
const initializeSchema = async () => {
|
|
try {
|
|
// Create constraints for unique IDs
|
|
await runQuery(
|
|
"CREATE CONSTRAINT episode_uuid IF NOT EXISTS FOR (n:Episode) REQUIRE n.uuid IS UNIQUE",
|
|
);
|
|
await runQuery(
|
|
"CREATE CONSTRAINT entity_uuid IF NOT EXISTS FOR (n:Entity) REQUIRE n.uuid IS UNIQUE",
|
|
);
|
|
await runQuery(
|
|
"CREATE CONSTRAINT statement_uuid IF NOT EXISTS FOR (n:Statement) REQUIRE n.uuid IS UNIQUE",
|
|
);
|
|
|
|
// Create indexes for better query performance
|
|
await runQuery(
|
|
"CREATE INDEX episode_valid_at IF NOT EXISTS FOR (n:Episode) ON (n.validAt)",
|
|
);
|
|
await runQuery(
|
|
"CREATE INDEX statement_valid_at IF NOT EXISTS FOR (n:Statement) ON (n.validAt)",
|
|
);
|
|
await runQuery(
|
|
"CREATE INDEX statement_invalid_at IF NOT EXISTS FOR (n:Statement) ON (n.invalidAt)",
|
|
);
|
|
await runQuery(
|
|
"CREATE INDEX entity_name IF NOT EXISTS FOR (n:Entity) ON (n.name)",
|
|
);
|
|
|
|
// Create vector indexes for semantic search (if using Neo4j 5.0+)
|
|
await runQuery(`
|
|
CREATE VECTOR INDEX entity_embedding IF NOT EXISTS FOR (n:Entity) ON n.nameEmbedding
|
|
OPTIONS {indexConfig: {\`vector.dimensions\`: 1536, \`vector.similarity_function\`: 'cosine'}}
|
|
`);
|
|
|
|
await runQuery(`
|
|
CREATE VECTOR INDEX statement_embedding IF NOT EXISTS FOR (n:Statement) ON n.factEmbedding
|
|
OPTIONS {indexConfig: {\`vector.dimensions\`: 1536, \`vector.similarity_function\`: 'cosine'}}
|
|
`);
|
|
|
|
await runQuery(`
|
|
CREATE VECTOR INDEX episode_embedding IF NOT EXISTS FOR (n:Episode) ON n.contentEmbedding
|
|
OPTIONS {indexConfig: {\`vector.dimensions\`: 1536, \`vector.similarity_function\`: 'cosine'}}
|
|
`);
|
|
|
|
// Create fulltext indexes for BM25 search
|
|
await runQuery(`
|
|
CREATE FULLTEXT INDEX statement_fact_index IF NOT EXISTS
|
|
FOR (n:Statement) ON EACH [n.fact]
|
|
OPTIONS {
|
|
indexConfig: {
|
|
\`fulltext.analyzer\`: 'english'
|
|
}
|
|
}
|
|
`);
|
|
|
|
await runQuery(`
|
|
CREATE FULLTEXT INDEX entity_name_index IF NOT EXISTS
|
|
FOR (n:Entity) ON EACH [n.name, n.description]
|
|
OPTIONS {
|
|
indexConfig: {
|
|
\`fulltext.analyzer\`: 'english'
|
|
}
|
|
}
|
|
`);
|
|
|
|
logger.info("Neo4j schema initialized successfully");
|
|
return true;
|
|
} catch (error) {
|
|
logger.error("Failed to initialize Neo4j schema", { error });
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// Close the driver when the application shuts down
|
|
const closeDriver = async () => {
|
|
await driver.close();
|
|
logger.info("Neo4j driver closed");
|
|
};
|
|
|
|
// await initializeSchema();
|
|
|
|
export { driver, verifyConnectivity, runQuery, initializeSchema, closeDriver };
|