From 1de7fe3f935f6655dcd4b691676180a3bf64ba0e Mon Sep 17 00:00:00 2001 From: RGBCube Date: Fri, 31 Oct 2025 21:09:29 +0300 Subject: [PATCH 1/7] examples: add bookstore --- examples/bookstore.py | 115 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 examples/bookstore.py diff --git a/examples/bookstore.py b/examples/bookstore.py new file mode 100644 index 000000000..8b700fc75 --- /dev/null +++ b/examples/bookstore.py @@ -0,0 +1,115 @@ +# N::Chapter { +# chapter_index: I64 +# } +# +# N::SubChapter { +# title: String, +# content: String +# } +# +# E::Contains { +# From: Chapter, +# To: SubChapter, +# Properties: { +# } +# } +# +# V::Embedding { +# chunk: String +# } +# +# E::EmbeddingOf { +# From: SubChapter, +# To: Embedding, +# Properties: { +# chunk: String +# } +# } + +# QUERY loaddocs_rag(chapters: [{ id: I64, subchapters: [{ title: String, content: String, chunks: [{chunk: String, vector: [F64]}]}] }]) => +# FOR {id, subchapters} IN chapters { +# chapter_node <- AddN({ chapter_index: id }) +# FOR {title, content, chunks} IN subchapters { +# subchapter_node <- AddN({ title: title, content: content }) +# AddE::From(chapter_node)::To(subchapter_node) +# FOR {chunk, vector} IN chunks { +# vec <- AddV(vector) +# AddE({chunk: chunk})::From(subchapter_node)::To(vec) +# } +# } +# } +# RETURN "Success" + +# QUERY searchdocs_rag(query: [F64], k: I32) => +# vecs <- SearchV(query, k) +# subchapters <- vecs::In +# RETURN subchapters::{title, content} + +# QUERY edge_node() => +# e <- N::OutE +# RETURN e + +# QUERY edge_node_id(id: ID) => +# e <- N::OutE(id) +# RETURN e +# + +import helix + +db = helix.Db() + +class Chapter(db.Node): + @index + index: helix.I64 + +class SubChapter(db.Node): + title: helix.String + content: helix.String + + embedding: EmbeddingVector + +class EmbeddingVector(db.Vector(dimensions=1536, hnsw=helix.cosine)): + pass + +class Contains(db.Edge[Chapter, SubChapter]): + pass + +class ArgChapter(helix.Struct): + id: helix.I64 + subchapters: helix.List[ArgSubchapter] + +class ArgSubchapter(helix.Struct): + title: helix.String + content: helix.String + chunk: helix.Vector + +@db.query +def loaddocs_rag(chapters: helix.List[ArgChapter]) -> str: + for c in chapters: + c_node = db.add_node(Chapter(index=c.id)) + + for sc in c.subchapters: + sc_node = db.add_node(SubChapter( + title=sc.title, + content=sc.content, + embedding=EmbeddingVector(sc.chunk) + )) + + db.add_edge(Contains(from=c_node, to=sc_node)) + + return "Success" + +@db.query +def searchdocs_rag(query: helix.Vector, k: helix.I32) -> helix.Iterator[dict[str, helix.Value]]: + # TODO + vecs = db.search_vector(query, k) + chapters = vecs.incoming_nodes[Contains] + return chapters.map(lambda c: {"index": c.index}) + +@db.query +def edge_node() -> helix.Iterator[Contains]: + return db.nodes[Chapter].outgoing_edges[Contains] + +@db.query +def edge_node_id(id: helix.Id[Chapter]) -> helix.Iterator[Contains]: + return db.nodes[Chapter](id=id).outgoing_edges[Contains] From 10ecaf2f22b1eb349431b0ef89edb17341898a42 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Thu, 13 Nov 2025 21:31:18 +0300 Subject: [PATCH 2/7] examples.bookstore: fix indexes --- examples/bookstore.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/examples/bookstore.py b/examples/bookstore.py index 8b700fc75..4fd25b06a 100644 --- a/examples/bookstore.py +++ b/examples/bookstore.py @@ -59,9 +59,10 @@ db = helix.Db() class Chapter(db.Node): - @index index: helix.I64 +db.index(Chapter.index, unique=True) + class SubChapter(db.Node): title: helix.String content: helix.String From 92c200fd177d0ad0ac7ecc4f7dff886ac82c6d12 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Thu, 13 Nov 2025 21:32:55 +0300 Subject: [PATCH 3/7] examples.bookstore: rename EmbeddingVector to SubChapterEmbedding --- examples/bookstore.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/bookstore.py b/examples/bookstore.py index 4fd25b06a..32258453e 100644 --- a/examples/bookstore.py +++ b/examples/bookstore.py @@ -67,9 +67,9 @@ class SubChapter(db.Node): title: helix.String content: helix.String - embedding: EmbeddingVector + embedding: SubChapterEmbedding -class EmbeddingVector(db.Vector(dimensions=1536, hnsw=helix.cosine)): +class SubChapterEmbedding(db.Vector(dimensions=1536, hnsw=helix.cosine)): pass class Contains(db.Edge[Chapter, SubChapter]): @@ -93,7 +93,7 @@ def loaddocs_rag(chapters: helix.List[ArgChapter]) -> str: sc_node = db.add_node(SubChapter( title=sc.title, content=sc.content, - embedding=EmbeddingVector(sc.chunk) + embedding=SubChapterEmbedding(sc.chunk) )) db.add_edge(Contains(from=c_node, to=sc_node)) From e892d2d2e35be3defc8a276893ee3a1d34502dd3 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Thu, 13 Nov 2025 21:58:16 +0300 Subject: [PATCH 4/7] examples.bookstore: use Helix types for all Query related operations --- examples/bookstore.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/bookstore.py b/examples/bookstore.py index 32258453e..b228c9401 100644 --- a/examples/bookstore.py +++ b/examples/bookstore.py @@ -85,7 +85,7 @@ class ArgSubchapter(helix.Struct): chunk: helix.Vector @db.query -def loaddocs_rag(chapters: helix.List[ArgChapter]) -> str: +def loaddocs_rag(chapters: helix.List[ArgChapter]) -> helix.String: for c in chapters: c_node = db.add_node(Chapter(index=c.id)) @@ -101,7 +101,7 @@ def loaddocs_rag(chapters: helix.List[ArgChapter]) -> str: return "Success" @db.query -def searchdocs_rag(query: helix.Vector, k: helix.I32) -> helix.Iterator[dict[str, helix.Value]]: +def searchdocs_rag(query: helix.Vector, k: helix.I32) -> helix.Iterator[helix.Map[helix.String, helix.Value]]: # TODO vecs = db.search_vector(query, k) chapters = vecs.incoming_nodes[Contains] From 5560cd8769b5e066ed5a262552698c31fc7309d4 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Tue, 18 Nov 2025 23:27:56 +0300 Subject: [PATCH 5/7] examples: add typescript example --- examples/bookstore.ts | 72 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 examples/bookstore.ts diff --git a/examples/bookstore.ts b/examples/bookstore.ts new file mode 100644 index 000000000..26458c9aa --- /dev/null +++ b/examples/bookstore.ts @@ -0,0 +1,72 @@ +import hx from "helix"; + +const schema = hx.schema(); + +const Chapter = schema.defineNode({ + index: I64, +}); + +schema.index(Chapter.index, { unique: true }); + +const SubChapter = schema.defineNode({ + title: hx.String, + content: hx.String, + + embedding: SubChapterEmbedding, +}); + +const SubChapterEmbedding = schema.defineVector({ + dimensions: 1536, + hnsw: hx.cosine, +}); + +const Contains = schema.defineEdge({ from: Chapter, to: SubChapter }); + +const ArgChapter = hx.Struct({ + id: hx.I64, + subchapters: hx.List(ArgSubchapter), +}); + +const ArgSubChapter = hx.Struct({ + title: hx.String, + content: hx.String, + chunk: hx.Vector, +}); + +const loadDocsRag = schema.query({ + name: "loaddocs_rag", + arguments: [hx.List(ArgChapter)], + returns: hx.String, +}, (db, [chapters]) => { + chapters.forEach((c) => { + const cNode = db.addNode(Chapter({ index: c.id })); + + c.subchapters.forEach((sc) => { + const scNode = db.addNode(SubChapter({ + title: sc.title, + content: sc.content, + embedding: SubChapterEmbedding(sc.chunk), + })); + + db.addEdge(Contains({ from: cNode, to: scNode })); + }); + }); + + return "Success"; +}); + +const edgeNode = schema.query({ + name: "edge_node", + arguments: [], + returns: hx.Iterator(Contains), +}, (db, []) => { + return db.nodes[Chapter].outgoingEdges[Contains]; +}); + +const edgeNodeId = schema.query({ + name: "edge_node_id", + arguments: [hx.Id(Chapter)], + returns: hx.Iterator(Contains), +}, (db, [id]) => { + return db.nodes[Chapter]({ id }).outgoingEdges[Contains]; +}); From 1a6df2c0140f28a03841e0654a071b98c3528449 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Fri, 21 Nov 2025 20:56:23 +0300 Subject: [PATCH 6/7] helix-ts: basic IR --- helix-ts/deno.json | 5 +++ helix-ts/ir.ts | 79 ++++++++++++++++++++++++++++++++++++++++++++++ helix-ts/main.ts | 3 ++ 3 files changed, 87 insertions(+) create mode 100644 helix-ts/deno.json create mode 100644 helix-ts/ir.ts create mode 100644 helix-ts/main.ts diff --git a/helix-ts/deno.json b/helix-ts/deno.json new file mode 100644 index 000000000..3c5130f1d --- /dev/null +++ b/helix-ts/deno.json @@ -0,0 +1,5 @@ +{ + "tasks": { + "dev": "deno run --watch main.ts" + } +} diff --git a/helix-ts/ir.ts b/helix-ts/ir.ts new file mode 100644 index 000000000..daa6cc733 --- /dev/null +++ b/helix-ts/ir.ts @@ -0,0 +1,79 @@ +export const ExprKindString = { kind: "String" as const }; +export const ExprKindI64 = { kind: "I64" as const }; +export const ExprKindF64 = { kind: "F64" as const }; +export const ExprKindBoolean = { kind: "Boolean" as const }; +export const ExprKindVector = { kind: "Vector" as const }; +export const ExprKindList = (item: ExprKind) => ({ + kind: "List" as const, + item, +}); +export const ExprKindStruct = (fields: Record) => ({ + kind: "Struct" as const, + fields, +}); + +export type ExprKind = + | typeof ExprKindString + | typeof ExprKindI64 + | typeof ExprKindF64 + | typeof ExprKindBoolean + | typeof ExprKindVector + | { kind: "List"; item: ExprKind } + | { kind: "Struct"; fields: Record }; + +export type Expr = { + kind: ExprKind; + expr: + | { expr: "Argument"; index: number } + | { expr: "PropAccess"; target: Expr; field: string } + | { expr: "Literal"; value: any } + | { expr: "BinaryOp"; op: string; left: Expr; right: Expr } + | { expr: "Call"; func: string; args: Expr[] }; +}; + +export type Statement = + | { stmt: "Expr"; expr: Expr } + | { stmt: "Return"; value: Expr }; + +export type Block = Statement[]; + +export type Query = { + name: string; + arguments: ExprKind[]; + returns: ExprKind; + body: Block; +}; + +export type NodeName = `node_${number}`; +export type Node = { + id: typeof ExprKindI64; + [_: string]: ExprKind; +}; + +export type EdgeName = `edge_${number}`; +export type Edge = { + id: typeof ExprKindI64; + from: NodeName; + to: NodeName; +}; + +export const GlobalVectorspaceName = `vectorspace_global` as const; +export type GlobalVectorspaceName = typeof GlobalVectorspaceName; +export type GlobalVectorspace = symbol; + +export type VectorspaceName = `vectorspace_${number}`; +export type Vectorspace = { + dimensions: number; + hnsw: any; // TODO +}; + +export type Schema = { + nodes: Record; + indices: [{ on: NodeName; field: string; unique: boolean }]; + + edges: Record; + + vectorspaces: + & Record + & Record; +}; diff --git a/helix-ts/main.ts b/helix-ts/main.ts new file mode 100644 index 000000000..76a3c6c65 --- /dev/null +++ b/helix-ts/main.ts @@ -0,0 +1,3 @@ +if (import.meta.main) { + console.log("it runs!"); +} From 161b6b8bad66c19b9ff92de6cd8b22e00b5e1a17 Mon Sep 17 00:00:00 2001 From: RGBCube Date: Fri, 21 Nov 2025 21:58:45 +0300 Subject: [PATCH 7/7] helix-ts: make toplevel schema entries named, and make expressions maybe named --- examples/bookstore.ts | 20 ++++++++++---------- helix-ts/ir.ts | 42 ++++++++++++++++++++++++------------------ 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/examples/bookstore.ts b/examples/bookstore.ts index 26458c9aa..e38555f1a 100644 --- a/examples/bookstore.ts +++ b/examples/bookstore.ts @@ -2,25 +2,28 @@ import hx from "helix"; const schema = hx.schema(); -const Chapter = schema.defineNode({ +const Chapter = schema.defineNode("Chapter", { index: I64, }); schema.index(Chapter.index, { unique: true }); -const SubChapter = schema.defineNode({ +const SubChapter = schema.defineNode("SubChapter", { title: hx.String, content: hx.String, embedding: SubChapterEmbedding, }); -const SubChapterEmbedding = schema.defineVector({ +const SubChapterEmbedding = schema.defineVector("SubChapterEmbedding", { dimensions: 1536, hnsw: hx.cosine, }); -const Contains = schema.defineEdge({ from: Chapter, to: SubChapter }); +const Contains = schema.defineEdge("Contains", { + from: Chapter, + to: SubChapter, +}); const ArgChapter = hx.Struct({ id: hx.I64, @@ -33,8 +36,7 @@ const ArgSubChapter = hx.Struct({ chunk: hx.Vector, }); -const loadDocsRag = schema.query({ - name: "loaddocs_rag", +const loadDocsRag = schema.query("loaddocs_rag", { arguments: [hx.List(ArgChapter)], returns: hx.String, }, (db, [chapters]) => { @@ -55,16 +57,14 @@ const loadDocsRag = schema.query({ return "Success"; }); -const edgeNode = schema.query({ - name: "edge_node", +const edgeNode = schema.query("edge_node", { arguments: [], returns: hx.Iterator(Contains), }, (db, []) => { return db.nodes[Chapter].outgoingEdges[Contains]; }); -const edgeNodeId = schema.query({ - name: "edge_node_id", +const edgeNodeId = schema.query("edge_node_id", { arguments: [hx.Id(Chapter)], returns: hx.Iterator(Contains), }, (db, [id]) => { diff --git a/helix-ts/ir.ts b/helix-ts/ir.ts index daa6cc733..c0863282a 100644 --- a/helix-ts/ir.ts +++ b/helix-ts/ir.ts @@ -13,13 +13,16 @@ export const ExprKindStruct = (fields: Record) => ({ }); export type ExprKind = - | typeof ExprKindString - | typeof ExprKindI64 - | typeof ExprKindF64 - | typeof ExprKindBoolean - | typeof ExprKindVector - | { kind: "List"; item: ExprKind } - | { kind: "Struct"; fields: Record }; + & MaybeNamed + & ( + | typeof ExprKindString + | typeof ExprKindI64 + | typeof ExprKindF64 + | typeof ExprKindBoolean + | typeof ExprKindVector + | { kind: "List"; item: ExprKind } + | { kind: "Struct"; fields: Record } + ); export type Expr = { kind: ExprKind; @@ -37,21 +40,24 @@ export type Statement = export type Block = Statement[]; -export type Query = { - name: string; +export type Named = { name: string }; +export type MaybeNamed = Partial; + +export type QueryName = string; +export type Query = Named & { arguments: ExprKind[]; returns: ExprKind; body: Block; }; -export type NodeName = `node_${number}`; -export type Node = { +export type NodeName = string; +export type Node = Named & { id: typeof ExprKindI64; [_: string]: ExprKind; }; -export type EdgeName = `edge_${number}`; -export type Edge = { +export type EdgeName = string; +export type Edge = Named & { id: typeof ExprKindI64; from: NodeName; to: NodeName; @@ -61,8 +67,8 @@ export const GlobalVectorspaceName = `vectorspace_global` as const; export type GlobalVectorspaceName = typeof GlobalVectorspaceName; export type GlobalVectorspace = symbol; -export type VectorspaceName = `vectorspace_${number}`; -export type Vectorspace = { +export type VectorspaceName = string; +export type Vectorspace = Named & { dimensions: number; hnsw: any; // TODO }; @@ -73,7 +79,7 @@ export type Schema = { edges: Record; - vectorspaces: - & Record - & Record; + vectorspaces: Record; + + queries: Record; };