Skip to main content

FalkorDB Graph Model

FalkorDB is not a cache or secondary store — it is the product. The entire scripture knowledge graph lives here: passages, topics, lexicon entries, manuscript witnesses, cross-references, and the relationships between them.

Why FalkorDB?

  • Redis-based — operational simplicity; same protocol and tooling as Redis
  • Cypher-compatible — standard graph query language
  • Performance — in-memory graph traversal, sub-millisecond for 2-3 hop queries
  • Chosen over Neo4j (licensing cost, ops complexity) and ArangoDB (less mature Cypher support)

The 7 Schema Families

Every JSON file in the corpus declares its type via a schema field. No heuristic detection is needed.

SchemaFile PatternDescription
scripture-textcorpus/{bookId}.jsonPassages with optional witnesses, words, notes
lexiconlexicon/{range}.jsonStrong's Hebrew/Greek dictionary entries
topical-guidetg/{letter}.jsonLDS Topical Guide topics
bible-dictionarybd/{letter}.jsonLDS Bible Dictionary articles
scripture-indexindex/{letter}.jsonCross-reference index
verse-commentarycommentary/{id}/{bookId}.jsonVerse-level scholarly commentary
scholarly-commentaryscholarly/{id}.jsonSection-level scholarly commentary

Node Types

graph LR
P["Passage"] --- W["Witness"]
P --- WA["WordAlignment"]
P --- N["VerseNote"]
P --- T["IndexTopic"]
T --- BD["BDArticle"]
WA --- L["LexiconEntry"]
Node LabelKey PropertiesExample
Passageid, text, book_id, chapter, verse, corpusgen.1.1
Witnesslanguage, script, text, witness, editionEthiopic text of 1 Enoch
WordAlignmentorder, gloss, strongs, tokenHebrew בְּרֵאשִׁ֖ית → "In the beginning"
LexiconEntryid, lemma, language, strongs_id, gloss, definitionH7225 → רֵאשִׁית
IndexTopicid, title, descriptiontg:angels
BDArticleid, title, bodybd:aaron
VerseNoteanchor, contentFootnote on Gen 1:1

Edge Types

EdgeFrom → ToPurpose
HAS_ORIGINALPassage → WitnessLinks passage to manuscript witness
CROSS_REFPassage → PassageScripture cross-reference
CITESIndexTopic → PassageTopic cites a passage
SEE_ALSO_TGIndexTopic → IndexTopicRelated topic link
SEE_ALSO_BDIndexTopic → BDArticleTopic → dictionary article
HAS_WORDPassage → WordAlignmentInterlinear word data
DEFINED_BYWordAlignment → LexiconEntryWord → lexicon definition
HAS_NOTEPassage → VerseNoteScholarly footnote

Shared Types

PassageRef

The canonical representation of a location reference:

interface PassageRef {
bookId: string // "gen", "1-enoch", "dc"
chapter: number // 1-based
verse?: number // 1-based; absent for chapter-only refs
verseEnd?: number // Inclusive end: verse 3–7 → verse:3, verseEnd:7
chapterEnd?: number // Cross-chapter ranges
}

Every cross-reference has an explicit type field — no positional inference:

TypeID FormatExample
topictg:angelsLink to Topical Guide entry
articlebd:angelsLink to Bible Dictionary entry
passagePassageRef objectCross-reference to another verse
personperson:aaron.1Link to person node
placeplace:ammonihahLink to place node

Display names (title/name) are denormalized on non-passage links for rendering without graph lookups.

Cypher Query Patterns

Passage Retrieval

MATCH (p:Passage {id: $passage_id})
OPTIONAL MATCH (p)-[:HAS_ORIGINAL]->(witness:Witness)
OPTIONAL MATCH (p)-[:CROSS_REF]->(ref:Passage)
WITH p, collect(DISTINCT witness) AS witnesses, collect(DISTINCT ref) AS refs
RETURN p, witnesses, refs

Topic Subgraph

MATCH (t:IndexTopic {id: $topic_id})
CALL {
WITH t
MATCH (t)-[:CITES]->(p:Passage)
RETURN 'passage' AS type, p AS node
UNION
WITH t
MATCH (t)-[:SEE_ALSO_TG]->(related:IndexTopic)
RETURN 'topic' AS type, related AS node
}
RETURN type, node
LIMIT $limit

Connection Discovery

MATCH (p:Passage {id: $passage_id})
MATCH (p)-[rel]->(connected)
RETURN type(rel) AS relationship_type,
labels(connected)[0] AS node_type,
connected.id AS connected_id,
connected.title AS connected_title,
rel.weight AS weight
ORDER BY rel.weight DESC
LIMIT $limit

MERGE Idempotency

All graph writes use MERGE (never CREATE) to ensure idempotency. The ingest pipeline can be re-run safely without creating duplicate nodes.

-- Idempotent node creation
MERGE (p:Passage {id: $id})
SET p.text = $text, p.book_id = $book_id, p.chapter = $chapter, p.verse = $verse

-- Idempotent edge creation
MATCH (a:Passage {id: $from_id}), (b:Passage {id: $to_id})
MERGE (a)-[:CROSS_REF]->(b)

Batch writes use UNWIND for performance:

UNWIND $passages AS p
MERGE (node:Passage {id: p.id})
SET node += p

Design Principles

  1. Every file declares its own type via the schema field
  2. All cross-references are structured objects (PassageRef), not human-formatted strings
  3. bookId is always a canonical slug — no integers, no display titles
  4. Optional fields are absent, not null or empty string
  5. camelCase throughout — no mixed conventions
  6. Presentation concerns excluded — no page numbers, URL slugs, or typesetting tokens