@danielzfliu/memory 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,195 @@
1
+ # Memory
2
+ A fully local Node.js library and REST API for storing, searching, and querying tagged text pieces using ChromaDB for vector storage and Ollama for embeddings + generation.
3
+
4
+ ## Prerequisites
5
+
6
+ - **Node.js** ≥ 18
7
+ - **Ollama** running locally ([install](https://ollama.com))
8
+ - **ChromaDB** server running locally
9
+
10
+ ### Start Ollama & pull models
11
+
12
+ ```bash
13
+ ollama pull nomic-embed-text-v2-moe
14
+ ollama pull llama3.2
15
+ npm run ollama
16
+ ```
17
+
18
+ or on a specific port:
19
+
20
+ ```bash
21
+ npm run ollama:port 11435
22
+ ```
23
+
24
+ ### Start ChromaDB
25
+ ```bash
26
+ npm run db
27
+ ```
28
+
29
+ or on a specific port:
30
+
31
+ ```bash
32
+ npm run db:port 9000
33
+ ```
34
+
35
+ **Windows note:** If `chroma` is not recognized, the `Scripts` directory may not be on your PATH. Either add it (e.g. `%APPDATA%\Python\Python3xx\Scripts`) or run the executable directly:
36
+ ```powershell
37
+ & "$env:APPDATA\Python\Python313\Scripts\chroma.exe" run --port 8000
38
+ ```
39
+
40
+ ## Install
41
+
42
+ ```bash
43
+ npm install
44
+ ```
45
+
46
+ ## Usage
47
+
48
+ ### REST API Server
49
+
50
+ ```bash
51
+ npm run dev
52
+ ```
53
+
54
+ Server starts on `http://localhost:3000` by default (set `PORT` env var to change).
55
+
56
+ ### API Endpoints
57
+
58
+ #### Add a piece
59
+ ```bash
60
+ curl -X POST http://localhost:3000/pieces \
61
+ -H "Content-Type: application/json" \
62
+ -d '{"content": "TypeScript is a typed superset of JavaScript.", "tags": ["typescript", "programming"]}'
63
+ ```
64
+
65
+ #### Get a piece by ID
66
+ ```bash
67
+ curl http://localhost:3000/pieces/<id>
68
+ ```
69
+
70
+ #### Update a piece
71
+ ```bash
72
+ curl -X PUT http://localhost:3000/pieces/<id> \
73
+ -H "Content-Type: application/json" \
74
+ -d '{"content": "Updated content.", "tags": ["new-tag"]}'
75
+ ```
76
+
77
+ #### Delete a piece
78
+ ```bash
79
+ curl -X DELETE http://localhost:3000/pieces/<id>
80
+ ```
81
+
82
+ #### Semantic search
83
+ ```bash
84
+ curl -X POST http://localhost:3000/query \
85
+ -H "Content-Type: application/json" \
86
+ -d '{"query": "What is TypeScript?", "topK": 5}'
87
+ ```
88
+
89
+ With tag filtering:
90
+ ```bash
91
+ curl -X POST http://localhost:3000/query \
92
+ -H "Content-Type: application/json" \
93
+ -d '{"query": "What is TypeScript?", "tags": ["programming"], "topK": 5}'
94
+ ```
95
+
96
+ #### RAG query (retrieve + generate)
97
+ ```bash
98
+ curl -X POST http://localhost:3000/rag \
99
+ -H "Content-Type: application/json" \
100
+ -d '{"query": "Explain TypeScript", "tags": ["programming"], "topK": 5}'
101
+ ```
102
+
103
+ Returns:
104
+ ```json
105
+ {
106
+ "answer": "Generated answer based on retrieved context...",
107
+ "sources": [
108
+ {
109
+ "piece": { "id": "...", "content": "...", "tags": ["..."] },
110
+ "score": 0.87
111
+ }
112
+ ]
113
+ }
114
+ ```
115
+
116
+ ### Programmatic Usage (Library)
117
+
118
+ ```typescript
119
+ import { PieceStore, RagPipeline, MemoryConfig } from "memory";
120
+
121
+ async function main() {
122
+ const config: MemoryConfig = {
123
+ chromaUrl: "http://localhost:8000",
124
+ ollamaUrl: "http://localhost:11434",
125
+ embeddingModel: "nomic-embed-text-v2-moe",
126
+ };
127
+
128
+ const store = new PieceStore(config);
129
+ await store.init();
130
+
131
+ await store.addPiece("TypeScript is a typed superset of JavaScript.", [
132
+ "typescript",
133
+ "programming",
134
+ ]);
135
+ await store.addPiece("Python is great for data science.", [
136
+ "python",
137
+ "data-science",
138
+ ]);
139
+
140
+ const results = await store.queryPieces("typed languages", { topK: 5 });
141
+ console.log("results", results);
142
+
143
+ const filtered = await store.queryPieces("typed languages", {
144
+ tags: ["typescript"],
145
+ topK: 5,
146
+ });
147
+ console.log("filtered", filtered);
148
+
149
+ const rag = new RagPipeline(store, "http://localhost:11434", "llama3.2");
150
+ const answer = await rag.query("What is TypeScript?", {
151
+ tags: ["programming"],
152
+ });
153
+ console.log("answer", answer);
154
+ }
155
+
156
+ main().catch((err) => {
157
+ console.error(err);
158
+ process.exit(1);
159
+ });
160
+ ```
161
+
162
+ ## Configuration (`MemoryConfig`)
163
+
164
+ | Option | Default | Description |
165
+ |--------|---------|-------------|
166
+ | `chromaUrl` | `http://localhost:8000` | ChromaDB server URL |
167
+ | `ollamaUrl` | `http://localhost:11434` | Ollama server URL |
168
+ | `embeddingModel` | `nomic-embed-text-v2-moe` | Ollama model for embeddings |
169
+ | `generationModel` | `llama3.2` | Ollama model for RAG generation |
170
+ | `collectionName` | `pieces` | ChromaDB collection name |
171
+
172
+ ## Testing
173
+
174
+ ```bash
175
+ npm test # run all tests
176
+ npm run test:watch # watch mode
177
+ npm run test:coverage # with coverage report
178
+ ```
179
+
180
+ ## Project Structure
181
+
182
+ ```
183
+ src/
184
+ ├── types.ts # Interfaces (MemoryConfig, Piece, QueryResult, etc.)
185
+ ├── embeddings.ts # Ollama embedding client
186
+ ├── store.ts # PieceStore — CRUD + semantic search + tag filtering
187
+ ├── rag.ts # RAG pipeline — retrieve → prompt → generate
188
+ ├── server.ts # Express REST API (app factory)
189
+ ├── main.ts # Server entry point (starts listening)
190
+ └── index.ts # Library entry point (public exports)
191
+ tests/
192
+ ├── helpers/ # Shared test fixtures (in-memory ChromaDB mock, etc.)
193
+ ├── unit/ # Unit tests (embeddings, store, rag)
194
+ └── integration/ # API integration tests (supertest)
195
+ ```
@@ -0,0 +1,8 @@
1
+ export declare class EmbeddingClient {
2
+ private readonly client;
3
+ private readonly model;
4
+ constructor(ollamaUrl: string, model: string);
5
+ embed(text: string): Promise<number[]>;
6
+ embedBatch(texts: string[]): Promise<number[][]>;
7
+ }
8
+ //# sourceMappingURL=embeddings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings.d.ts","sourceRoot":"","sources":["../src/embeddings.ts"],"names":[],"mappings":"AAEA,qBAAa,eAAe;IACxB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAKtC,KAAK,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAQtC,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC;CAOzD"}
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EmbeddingClient = void 0;
4
+ const ollama_1 = require("ollama");
5
+ class EmbeddingClient {
6
+ client;
7
+ model;
8
+ constructor(ollamaUrl, model) {
9
+ this.client = new ollama_1.Ollama({ host: ollamaUrl });
10
+ this.model = model;
11
+ }
12
+ async embed(text) {
13
+ const response = await this.client.embed({
14
+ model: this.model,
15
+ input: text,
16
+ });
17
+ return response.embeddings[0];
18
+ }
19
+ async embedBatch(texts) {
20
+ const response = await this.client.embed({
21
+ model: this.model,
22
+ input: texts,
23
+ });
24
+ return response.embeddings;
25
+ }
26
+ }
27
+ exports.EmbeddingClient = EmbeddingClient;
28
+ //# sourceMappingURL=embeddings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embeddings.js","sourceRoot":"","sources":["../src/embeddings.ts"],"names":[],"mappings":";;;AAAA,mCAAgC;AAEhC,MAAa,eAAe;IACP,MAAM,CAAS;IACf,KAAK,CAAS;IAE/B,YAAY,SAAiB,EAAE,KAAa;QACxC,IAAI,CAAC,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,IAAY;QACpB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YACrC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK,EAAE,IAAI;SACd,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,KAAK,CAAC,UAAU,CAAC,KAAe;QAC5B,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC;YACrC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,KAAK,EAAE,KAAK;SACf,CAAC,CAAC;QACH,OAAO,QAAQ,CAAC,UAAU,CAAC;IAC/B,CAAC;CACJ;AAxBD,0CAwBC"}
@@ -0,0 +1,6 @@
1
+ export { PieceStore } from "./store";
2
+ export { RagPipeline } from "./rag";
3
+ export { EmbeddingClient } from "./embeddings";
4
+ export { createServer } from "./server";
5
+ export { Piece, MemoryConfig, DEFAULT_MEMORY_CONFIG, QueryOptions, QueryResult, RagResult, } from "./types";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AACpC,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAC/C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EACH,KAAK,EACL,YAAY,EACZ,qBAAqB,EACrB,YAAY,EACZ,WAAW,EACX,SAAS,GACZ,MAAM,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_MEMORY_CONFIG = exports.createServer = exports.EmbeddingClient = exports.RagPipeline = exports.PieceStore = void 0;
4
+ var store_1 = require("./store");
5
+ Object.defineProperty(exports, "PieceStore", { enumerable: true, get: function () { return store_1.PieceStore; } });
6
+ var rag_1 = require("./rag");
7
+ Object.defineProperty(exports, "RagPipeline", { enumerable: true, get: function () { return rag_1.RagPipeline; } });
8
+ var embeddings_1 = require("./embeddings");
9
+ Object.defineProperty(exports, "EmbeddingClient", { enumerable: true, get: function () { return embeddings_1.EmbeddingClient; } });
10
+ var server_1 = require("./server");
11
+ Object.defineProperty(exports, "createServer", { enumerable: true, get: function () { return server_1.createServer; } });
12
+ var types_1 = require("./types");
13
+ Object.defineProperty(exports, "DEFAULT_MEMORY_CONFIG", { enumerable: true, get: function () { return types_1.DEFAULT_MEMORY_CONFIG; } });
14
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;AAAA,iCAAqC;AAA5B,mGAAA,UAAU,OAAA;AACnB,6BAAoC;AAA3B,kGAAA,WAAW,OAAA;AACpB,2CAA+C;AAAtC,6GAAA,eAAe,OAAA;AACxB,mCAAwC;AAA/B,sGAAA,YAAY,OAAA;AACrB,iCAOiB;AAJb,8GAAA,qBAAqB,OAAA"}
package/dist/main.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=main.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.d.ts","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":""}
package/dist/main.js ADDED
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const server_1 = require("./server");
4
+ const PORT = process.env.PORT ?? 3000;
5
+ const app = (0, server_1.createServer)();
6
+ app.listen(PORT, () => {
7
+ console.log(`Memory RAG server running on http://localhost:${PORT}`);
8
+ });
9
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":";;AAAA,qCAAwC;AAExC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,IAAI,IAAI,IAAI,CAAC;AACtC,MAAM,GAAG,GAAG,IAAA,qBAAY,GAAE,CAAC;AAC3B,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;IAClB,OAAO,CAAC,GAAG,CAAC,iDAAiD,IAAI,EAAE,CAAC,CAAC;AACzE,CAAC,CAAC,CAAC"}
package/dist/rag.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { PieceStore } from "./store";
2
+ import { QueryOptions, RagResult } from "./types";
3
+ export declare class RagPipeline {
4
+ private readonly store;
5
+ private readonly ollama;
6
+ private readonly model;
7
+ constructor(store: PieceStore, ollamaUrl: string, model: string);
8
+ query(query: string, options?: QueryOptions): Promise<RagResult>;
9
+ }
10
+ //# sourceMappingURL=rag.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rag.d.ts","sourceRoot":"","sources":["../src/rag.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAElD,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;gBAEnB,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM;IAMzD,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,SAAS,CAAC;CAuC7E"}
package/dist/rag.js ADDED
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RagPipeline = void 0;
4
+ const ollama_1 = require("ollama");
5
+ class RagPipeline {
6
+ store;
7
+ ollama;
8
+ model;
9
+ constructor(store, ollamaUrl, model) {
10
+ this.store = store;
11
+ this.ollama = new ollama_1.Ollama({ host: ollamaUrl });
12
+ this.model = model;
13
+ }
14
+ async query(query, options = {}) {
15
+ const sources = await this.store.queryPieces(query, options);
16
+ if (sources.length === 0) {
17
+ return {
18
+ answer: "I don't have enough context to answer this question. " +
19
+ "No relevant pieces were found in the knowledge base.",
20
+ sources: [],
21
+ };
22
+ }
23
+ const contextBlock = sources
24
+ .map((s, i) => `[${i + 1}] (tags: ${s.piece.tags.join(", ")})\n${s.piece.content}`)
25
+ .join("\n\n");
26
+ const systemPrompt = "You are a helpful assistant. Answer the user's question based on the provided context. " +
27
+ "If the context does not contain enough information, say so. " +
28
+ "Cite sources by their number when relevant.";
29
+ const userPrompt = `Context:\n${contextBlock}\n\nQuestion: ${query}`;
30
+ const response = await this.ollama.chat({
31
+ model: this.model,
32
+ messages: [
33
+ { role: "system", content: systemPrompt },
34
+ { role: "user", content: userPrompt },
35
+ ],
36
+ });
37
+ return {
38
+ answer: response.message.content,
39
+ sources,
40
+ };
41
+ }
42
+ }
43
+ exports.RagPipeline = RagPipeline;
44
+ //# sourceMappingURL=rag.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rag.js","sourceRoot":"","sources":["../src/rag.ts"],"names":[],"mappings":";;;AAAA,mCAAgC;AAIhC,MAAa,WAAW;IACH,KAAK,CAAa;IAClB,MAAM,CAAS;IACf,KAAK,CAAS;IAE/B,YAAY,KAAiB,EAAE,SAAiB,EAAE,KAAa;QAC3D,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,MAAM,GAAG,IAAI,eAAM,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACvB,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAa,EAAE,UAAwB,EAAE;QACjD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;QAE7D,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO;gBACH,MAAM,EACF,uDAAuD;oBACvD,sDAAsD;gBAC1D,OAAO,EAAE,EAAE;aACd,CAAC;QACN,CAAC;QAED,MAAM,YAAY,GAAG,OAAO;aACvB,GAAG,CACA,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CACL,IAAI,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC,OAAO,EAAE,CAC1E;aACA,IAAI,CAAC,MAAM,CAAC,CAAC;QAElB,MAAM,YAAY,GACd,yFAAyF;YACzF,8DAA8D;YAC9D,6CAA6C,CAAC;QAElD,MAAM,UAAU,GAAG,aAAa,YAAY,iBAAiB,KAAK,EAAE,CAAC;QAErE,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;YACpC,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,QAAQ,EAAE;gBACN,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;gBACzC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE;aACxC;SACJ,CAAC,CAAC;QAEH,OAAO;YACH,MAAM,EAAE,QAAQ,CAAC,OAAO,CAAC,OAAO;YAChC,OAAO;SACV,CAAC;IACN,CAAC;CACJ;AAlDD,kCAkDC"}
@@ -0,0 +1,3 @@
1
+ import { MemoryConfig } from "./types";
2
+ export declare function createServer(config?: MemoryConfig): import("express-serve-static-core").Express;
3
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,YAAY,EAAyB,MAAM,SAAS,CAAC;AAE9D,wBAAgB,YAAY,CAAC,MAAM,GAAE,YAAiB,+CAwHrD"}
package/dist/server.js ADDED
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createServer = createServer;
7
+ const express_1 = __importDefault(require("express"));
8
+ const store_1 = require("./store");
9
+ const rag_1 = require("./rag");
10
+ const types_1 = require("./types");
11
+ function createServer(config = {}) {
12
+ const resolvedConfig = { ...types_1.DEFAULT_MEMORY_CONFIG, ...config };
13
+ const app = (0, express_1.default)();
14
+ app.use(express_1.default.json());
15
+ const store = new store_1.PieceStore(resolvedConfig);
16
+ const rag = new rag_1.RagPipeline(store, resolvedConfig.ollamaUrl, resolvedConfig.generationModel);
17
+ // Middleware to ensure store is initialized (uses a cached promise to
18
+ // avoid duplicate init() calls when concurrent requests arrive early)
19
+ let initPromise = null;
20
+ app.use(async (_req, res, next) => {
21
+ if (!initPromise) {
22
+ initPromise = store.init();
23
+ }
24
+ try {
25
+ await initPromise;
26
+ }
27
+ catch (err) {
28
+ initPromise = null; // allow retry on next request
29
+ res.status(503).json({
30
+ error: "Failed to connect to ChromaDB",
31
+ details: String(err),
32
+ });
33
+ return;
34
+ }
35
+ next();
36
+ });
37
+ // POST /pieces — Add a piece
38
+ app.post("/pieces", async (req, res) => {
39
+ try {
40
+ const { content, tags } = req.body;
41
+ if (!content || typeof content !== "string") {
42
+ res.status(400).json({ error: "content (string) is required" });
43
+ return;
44
+ }
45
+ const piece = await store.addPiece(content, tags ?? []);
46
+ res.status(201).json(piece);
47
+ }
48
+ catch (err) {
49
+ res.status(500).json({ error: String(err) });
50
+ }
51
+ });
52
+ // GET /pieces/:id — Get a piece by ID
53
+ app.get("/pieces/:id", async (req, res) => {
54
+ try {
55
+ const { id } = req.params;
56
+ const piece = await store.getPiece(id);
57
+ if (!piece) {
58
+ res.status(404).json({ error: "Piece not found" });
59
+ return;
60
+ }
61
+ res.json(piece);
62
+ }
63
+ catch (err) {
64
+ res.status(500).json({ error: String(err) });
65
+ }
66
+ });
67
+ // PUT /pieces/:id — Update a piece
68
+ app.put("/pieces/:id", async (req, res) => {
69
+ try {
70
+ const { id } = req.params;
71
+ const { content, tags } = req.body;
72
+ const piece = await store.updatePiece(id, content, tags);
73
+ if (!piece) {
74
+ res.status(404).json({ error: "Piece not found" });
75
+ return;
76
+ }
77
+ res.json(piece);
78
+ }
79
+ catch (err) {
80
+ res.status(500).json({ error: String(err) });
81
+ }
82
+ });
83
+ // DELETE /pieces/:id — Delete a piece
84
+ app.delete("/pieces/:id", async (req, res) => {
85
+ try {
86
+ const { id } = req.params;
87
+ await store.deletePiece(id);
88
+ res.status(204).send();
89
+ }
90
+ catch (err) {
91
+ res.status(500).json({ error: String(err) });
92
+ }
93
+ });
94
+ // POST /query — Semantic search
95
+ app.post("/query", async (req, res) => {
96
+ try {
97
+ const { query, tags, topK } = req.body;
98
+ if (!query || typeof query !== "string") {
99
+ res.status(400).json({ error: "query (string) is required" });
100
+ return;
101
+ }
102
+ const results = await store.queryPieces(query, { tags, topK });
103
+ res.json(results);
104
+ }
105
+ catch (err) {
106
+ res.status(500).json({ error: String(err) });
107
+ }
108
+ });
109
+ // POST /rag — Full RAG query
110
+ app.post("/rag", async (req, res) => {
111
+ try {
112
+ const { query, tags, topK } = req.body;
113
+ if (!query || typeof query !== "string") {
114
+ res.status(400).json({ error: "query (string) is required" });
115
+ return;
116
+ }
117
+ const result = await rag.query(query, { tags, topK });
118
+ res.json(result);
119
+ }
120
+ catch (err) {
121
+ res.status(500).json({ error: String(err) });
122
+ }
123
+ });
124
+ return app;
125
+ }
126
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { Piece, MemoryConfig, QueryOptions, QueryResult } from "./types";
2
+ export declare class PieceStore {
3
+ private readonly chromaClient;
4
+ private readonly embeddingClient;
5
+ private collection;
6
+ private readonly config;
7
+ constructor(config?: MemoryConfig);
8
+ init(): Promise<void>;
9
+ private getCollection;
10
+ addPiece(content: string, tags: string[]): Promise<Piece>;
11
+ getPiece(id: string): Promise<Piece | null>;
12
+ deletePiece(id: string): Promise<void>;
13
+ updatePiece(id: string, content?: string, tags?: string[]): Promise<Piece | null>;
14
+ queryPieces(query: string, options?: QueryOptions): Promise<QueryResult[]>;
15
+ }
16
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAGA,OAAO,EACH,KAAK,EACL,YAAY,EAEZ,YAAY,EACZ,WAAW,EACd,MAAM,SAAS,CAAC;AAYjB,qBAAa,UAAU;IACnB,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAe;IAC5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAkB;IAClD,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAyB;gBAEpC,MAAM,GAAE,YAAiB;IAS/B,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,OAAO,CAAC,aAAa;IAOf,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC;IAezD,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IAgB3C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKtC,WAAW,CACb,EAAE,EAAE,MAAM,EACV,OAAO,CAAC,EAAE,MAAM,EAChB,IAAI,CAAC,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC;IA8BlB,WAAW,CACb,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,YAAiB,GAC3B,OAAO,CAAC,WAAW,EAAE,CAAC;CAiD5B"}
package/dist/store.js ADDED
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PieceStore = void 0;
4
+ const chromadb_1 = require("chromadb");
5
+ const uuid_1 = require("uuid");
6
+ const embeddings_1 = require("./embeddings");
7
+ const types_1 = require("./types");
8
+ function toChromaMetadata(tags) {
9
+ return { tags };
10
+ }
11
+ function parseTags(metadata) {
12
+ return metadata?.tags ?? [];
13
+ }
14
+ class PieceStore {
15
+ chromaClient;
16
+ embeddingClient;
17
+ collection = null;
18
+ config;
19
+ constructor(config = {}) {
20
+ this.config = { ...types_1.DEFAULT_MEMORY_CONFIG, ...config };
21
+ this.chromaClient = new chromadb_1.ChromaClient({ path: this.config.chromaUrl });
22
+ this.embeddingClient = new embeddings_1.EmbeddingClient(this.config.ollamaUrl, this.config.embeddingModel);
23
+ }
24
+ async init() {
25
+ this.collection = await this.chromaClient.getOrCreateCollection({
26
+ name: this.config.collectionName,
27
+ metadata: { "hnsw:space": "cosine" },
28
+ });
29
+ }
30
+ getCollection() {
31
+ if (!this.collection) {
32
+ throw new Error("PieceStore not initialized. Call init() first.");
33
+ }
34
+ return this.collection;
35
+ }
36
+ async addPiece(content, tags) {
37
+ const collection = this.getCollection();
38
+ const id = (0, uuid_1.v4)();
39
+ const embedding = await this.embeddingClient.embed(content);
40
+ await collection.add({
41
+ ids: [id],
42
+ embeddings: [embedding],
43
+ documents: [content],
44
+ metadatas: [toChromaMetadata(tags)],
45
+ });
46
+ return { id, content, tags };
47
+ }
48
+ async getPiece(id) {
49
+ const collection = this.getCollection();
50
+ const result = await collection.get({
51
+ ids: [id],
52
+ include: [chromadb_1.IncludeEnum.Documents, chromadb_1.IncludeEnum.Metadatas],
53
+ });
54
+ if (!result.ids.length)
55
+ return null;
56
+ return {
57
+ id: result.ids[0],
58
+ content: result.documents[0] ?? "",
59
+ tags: parseTags(result.metadatas[0]),
60
+ };
61
+ }
62
+ async deletePiece(id) {
63
+ const collection = this.getCollection();
64
+ await collection.delete({ ids: [id] });
65
+ }
66
+ async updatePiece(id, content, tags) {
67
+ const collection = this.getCollection();
68
+ const existing = await this.getPiece(id);
69
+ if (!existing)
70
+ return null;
71
+ const newContent = content ?? existing.content;
72
+ const newTags = tags ?? existing.tags;
73
+ const updateData = {
74
+ ids: [id],
75
+ metadatas: [toChromaMetadata(newTags)],
76
+ };
77
+ if (content !== undefined) {
78
+ updateData.documents = [newContent];
79
+ updateData.embeddings = [
80
+ await this.embeddingClient.embed(newContent),
81
+ ];
82
+ }
83
+ await collection.update(updateData);
84
+ return { id, content: newContent, tags: newTags };
85
+ }
86
+ async queryPieces(query, options = {}) {
87
+ const collection = this.getCollection();
88
+ const { tags, topK = 10 } = options;
89
+ const queryEmbedding = await this.embeddingClient.embed(query);
90
+ let whereClause;
91
+ if (tags && tags.length > 0) {
92
+ if (tags.length === 1) {
93
+ whereClause = { tags: { $contains: tags[0] } };
94
+ }
95
+ else {
96
+ whereClause = {
97
+ $and: tags.map((tag) => ({
98
+ tags: { $contains: tag },
99
+ })),
100
+ };
101
+ }
102
+ }
103
+ const results = await collection.query({
104
+ queryEmbeddings: [queryEmbedding],
105
+ nResults: topK,
106
+ where: whereClause,
107
+ include: [
108
+ chromadb_1.IncludeEnum.Documents,
109
+ chromadb_1.IncludeEnum.Metadatas,
110
+ chromadb_1.IncludeEnum.Distances,
111
+ ],
112
+ });
113
+ const queryResults = [];
114
+ const ids = results.ids[0] ?? [];
115
+ const documents = results.documents[0] ?? [];
116
+ const metadatas = results.metadatas[0] ?? [];
117
+ const distances = results.distances?.[0] ?? [];
118
+ for (let i = 0; i < ids.length; i++) {
119
+ queryResults.push({
120
+ piece: {
121
+ id: ids[i],
122
+ content: documents[i] ?? "",
123
+ tags: parseTags(metadatas[i]),
124
+ },
125
+ score: 1 - (distances[i] ?? 0), // cosine distance → similarity
126
+ });
127
+ }
128
+ return queryResults;
129
+ }
130
+ }
131
+ exports.PieceStore = PieceStore;
132
+ //# sourceMappingURL=store.js.map