110 lines
3.9 KiB
JavaScript
110 lines
3.9 KiB
JavaScript
const {getDB} = require('../db');
|
|
const { parseRow } = require ('@nexusai/shared')
|
|
|
|
/******* Entities ********/
|
|
|
|
// Upsert an entity - insert or update if (name, type) already exists
|
|
function upsertEntity(name, type, notes = null, metadata = null, source = 'extraction') {
|
|
const db = getDB();
|
|
const stmt = db.prepare(`
|
|
INSERT INTO entities (name, type, notes, metadata, source, last_seen_at)
|
|
VALUES (?, ?, ?, ?, ?, unixepoch())
|
|
ON CONFLICT(name, type) DO UPDATE SET
|
|
-- First extraction wins: notes are never overwritten once set.
|
|
-- Revisit during Memory Consolidation Lifecycle (Phase 2) — once entity
|
|
-- quality scoring exists, a higher-confidence extraction should be able
|
|
-- to replace stale notes rather than being silently dropped.
|
|
notes = COALESCE(entities.notes, excluded.notes),
|
|
metadata = excluded.metadata,
|
|
mention_count = entities.mention_count + 1,
|
|
last_seen_at = unixepoch(),
|
|
updated_at = unixepoch()
|
|
`);
|
|
stmt.run(name, type, notes, metadata ? JSON.stringify(metadata) : null, source);
|
|
return getEntityByNameType(name, type);
|
|
}
|
|
|
|
// Get an entity by its ID
|
|
function getEntity(id) {
|
|
const db = getDB();
|
|
return parseRow(db.prepare(`SELECT * FROM entities WHERE id = ?`).get(id));
|
|
}
|
|
|
|
// Get all entities of a given type
|
|
function getEntitiesByType(type) {
|
|
const db = getDB();
|
|
return db.prepare(`SELECT * FROM entities WHERE type = ? ORDER BY name`).all(type).map(parseRow);
|
|
}
|
|
|
|
// Delete an entity by ID, cascades to delete relationships involving this entity
|
|
function deleteEntity(id) {
|
|
const db = getDB();
|
|
db.prepare(`DELETE FROM entities WHERE id = ?`).run(id);
|
|
}
|
|
|
|
/********* Relationships *********/
|
|
|
|
// Upsert a relationship, insert or ignore if (from_id, to_id, label) already exists
|
|
function upsertRelationship(fromId, toId, label, notes = null, metadata = null) {
|
|
const db = getDB();
|
|
const stmt = db.prepare(`
|
|
INSERT INTO relationships (from_id, to_id, label, notes, metadata)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
ON CONFLICT(from_id, to_id, label) DO UPDATE SET
|
|
mention_count = relationships.mention_count + 1,
|
|
-- First extraction wins for notes — same policy as entities.
|
|
notes = COALESCE(relationships.notes, excluded.notes)
|
|
`);
|
|
stmt.run(fromId, toId, label, notes, metadata ? JSON.stringify(metadata) : null);
|
|
return getRelationship(fromId, toId, label);
|
|
}
|
|
|
|
// Retrieve a relationship by (from_id, to_id, label)
|
|
function getRelationship(fromId, toId, label) {
|
|
const db = getDB();
|
|
|
|
return parseRow(
|
|
db.prepare(`SELECT * FROM relationships WHERE from_id = ? AND to_id = ? AND label = ?`)
|
|
.get(fromId, toId, label)
|
|
);
|
|
}
|
|
|
|
// Retrieves an entity by its unique (name, type) combination
|
|
function getEntityByNameType(name, type) {
|
|
const db = getDB();
|
|
return parseRow(db.prepare(`SELECT * FROM entities WHERE name = ? AND type = ?`).get(name, type));
|
|
}
|
|
|
|
// Retrive all relationships originating from a given entity
|
|
function getOutboundRelationships(entityId) {
|
|
const db = getDB();
|
|
return db.prepare(`SELECT * FROM relationships WHERE from_id = ?`).all(entityId).map(parseRow);
|
|
}
|
|
|
|
// Delete a specific relationship by (from_id, to_id, label)
|
|
function deleteRelationship(fromId, toId, label) {
|
|
const db = getDB();
|
|
|
|
db.prepare(`DELETE FROM relationships WHERE from_id = ? AND to_id = ? AND label = ?`).run(fromId, toId, label);
|
|
}
|
|
|
|
function linkEntityToEpisode(entityId, episodeId) {
|
|
const db = getDB();
|
|
db.prepare(`
|
|
INSERT OR IGNORE INTO entity_episodes (entity_id, episode_id)
|
|
VALUES (?, ?)
|
|
`).run(entityId, episodeId);
|
|
}
|
|
|
|
module.exports = {
|
|
upsertEntity,
|
|
getEntity,
|
|
getEntitiesByType,
|
|
getEntityByNameType,
|
|
deleteEntity,
|
|
linkEntityToEpisode,
|
|
upsertRelationship,
|
|
getRelationship,
|
|
getOutboundRelationships,
|
|
deleteRelationship
|
|
} |