@lupaflow/memory 0.1.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/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@lupaflow/memory",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "publishConfig": {
7
+ "access": "public"
8
+ },
9
+ "main": "./dist/index.js",
10
+ "module": "./dist/index.js",
11
+ "types": "./dist/index.d.ts",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./dist/index.d.ts",
15
+ "require": "./dist/index.js",
16
+ "import": "./dist/index.js"
17
+ }
18
+ },
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "dev": "tsup --watch"
22
+ },
23
+ "dependencies": {
24
+ "@lupaflow/core": "workspace:*",
25
+ "@lupaflow/types": "workspace:*"
26
+ },
27
+ "devDependencies": {
28
+ "tsup": "^8.3.0",
29
+ "typescript": "^5.6.0"
30
+ }
31
+ }
package/src/index.ts ADDED
@@ -0,0 +1,9 @@
1
+ export { MemoryStore } from "./store"
2
+ export { ShortTermMemory } from "./short-term"
3
+ export { LongTermMemory } from "./long-term"
4
+ export { SemanticMemory } from "./semantic"
5
+ export { SessionMemory } from "./session"
6
+ export { UserMemory } from "./user"
7
+ export { ProjectMemory } from "./project"
8
+ export { VectorMemory } from "./vector"
9
+ export type { MemoryBackend } from "./interface"
@@ -0,0 +1,11 @@
1
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult, MemoryType } from "@lupaflow/types"
2
+
3
+ export interface MemoryBackend {
4
+ readonly type: MemoryType
5
+
6
+ store(entry: MemoryEntry): Promise<void>
7
+ retrieve(query: MemoryQuery): Promise<MemoryEntry[]>
8
+ search(query: string, limit?: number): Promise<MemorySearchResult[]>
9
+ delete(key: string): Promise<void>
10
+ clear(): Promise<void>
11
+ }
@@ -0,0 +1,70 @@
1
+ import { readFile, writeFile, access } from "fs/promises"
2
+ import { join } from "path"
3
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult } from "@lupaflow/types"
4
+ import type { MemoryBackend } from "./interface"
5
+ import { generateId } from "@lupaflow/core"
6
+
7
+ export class LongTermMemory implements MemoryBackend {
8
+ readonly type = "long-term" as const
9
+ private entries: MemoryEntry[] = []
10
+ private filePath: string
11
+
12
+ constructor(name = "default") {
13
+ this.filePath = join(process.cwd(), ".lupaflow", `memory-${name}.json`)
14
+ }
15
+
16
+ async init(): Promise<void> {
17
+ try {
18
+ await access(this.filePath)
19
+ const data = await readFile(this.filePath, "utf-8")
20
+ this.entries = JSON.parse(data)
21
+ } catch {
22
+ this.entries = []
23
+ }
24
+ }
25
+
26
+ async persist(): Promise<void> {
27
+ const dir = join(process.cwd(), ".lupaflow")
28
+ try {
29
+ await access(dir)
30
+ } catch {
31
+ await writeFile(dir, "", { flag: "a" })
32
+ }
33
+ await writeFile(this.filePath, JSON.stringify(this.entries, null, 2))
34
+ }
35
+
36
+ async store(entry: MemoryEntry): Promise<void> {
37
+ this.entries.push({
38
+ ...entry,
39
+ id: entry.id || generateId(),
40
+ timestamp: entry.timestamp || Date.now(),
41
+ })
42
+ await this.persist()
43
+ }
44
+
45
+ async retrieve(query: MemoryQuery): Promise<MemoryEntry[]> {
46
+ let results = this.entries
47
+ if (query.key) results = results.filter((e) => e.key === query.key)
48
+ if (query.type) results = results.filter((e) => e.type === query.type)
49
+ if (query.scope) results = results.filter((e) => e.scope === query.scope)
50
+ return results.slice(-(query.limit || 50))
51
+ }
52
+
53
+ async search(_query: string, limit = 10): Promise<MemorySearchResult[]> {
54
+ const q = _query.toLowerCase()
55
+ return this.entries
56
+ .filter((e) => e.content.toLowerCase().includes(q))
57
+ .slice(-limit)
58
+ .map((entry) => ({ entry, score: 1 }))
59
+ }
60
+
61
+ async delete(key: string): Promise<void> {
62
+ this.entries = this.entries.filter((e) => e.key !== key)
63
+ await this.persist()
64
+ }
65
+
66
+ async clear(): Promise<void> {
67
+ this.entries = []
68
+ await this.persist()
69
+ }
70
+ }
package/src/project.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult } from "@lupaflow/types"
2
+ import type { MemoryBackend } from "./interface"
3
+ import { generateId } from "@lupaflow/core"
4
+ import { readFile, writeFile, access } from "fs/promises"
5
+
6
+ const PROJECT_MEMORY_FILE = ".lupaflow/project-memory.json"
7
+
8
+ export class ProjectMemory implements MemoryBackend {
9
+ readonly type = "project" as const
10
+ private entries: MemoryEntry[] = []
11
+ private loaded = false
12
+
13
+ async ensureLoaded(): Promise<void> {
14
+ if (this.loaded) return
15
+ try {
16
+ await access(PROJECT_MEMORY_FILE)
17
+ const data = await readFile(PROJECT_MEMORY_FILE, "utf-8")
18
+ this.entries = JSON.parse(data)
19
+ } catch {
20
+ this.entries = []
21
+ }
22
+ this.loaded = true
23
+ }
24
+
25
+ async persist(): Promise<void> {
26
+ await writeFile(PROJECT_MEMORY_FILE, JSON.stringify(this.entries, null, 2))
27
+ }
28
+
29
+ async store(entry: MemoryEntry): Promise<void> {
30
+ await this.ensureLoaded()
31
+ this.entries.push({
32
+ ...entry,
33
+ id: entry.id || generateId(),
34
+ scope: "project",
35
+ timestamp: entry.timestamp || Date.now(),
36
+ })
37
+ await this.persist()
38
+ }
39
+
40
+ async retrieve(query: MemoryQuery): Promise<MemoryEntry[]> {
41
+ await this.ensureLoaded()
42
+ let results = this.entries.filter((e) => e.scope === "project")
43
+ if (query.key) results = results.filter((e) => e.key === query.key)
44
+ return results.slice(-(query.limit || 50))
45
+ }
46
+
47
+ async search(_query: string, limit = 10): Promise<MemorySearchResult[]> {
48
+ await this.ensureLoaded()
49
+ const q = _query.toLowerCase()
50
+ return this.entries
51
+ .filter((e) => e.scope === "project" && e.content.toLowerCase().includes(q))
52
+ .slice(-limit)
53
+ .map((entry) => ({ entry, score: 1 }))
54
+ }
55
+
56
+ async delete(key: string): Promise<void> {
57
+ await this.ensureLoaded()
58
+ this.entries = this.entries.filter((e) => !(e.key === key && e.scope === "project"))
59
+ await this.persist()
60
+ }
61
+
62
+ async clear(): Promise<void> {
63
+ await this.ensureLoaded()
64
+ this.entries = this.entries.filter((e) => e.scope !== "project")
65
+ await this.persist()
66
+ }
67
+ }
@@ -0,0 +1,70 @@
1
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult } from "@lupaflow/types"
2
+ import type { MemoryBackend } from "./interface"
3
+ import { generateId } from "@lupaflow/core"
4
+
5
+ function cosineSimilarity(a: number[], b: number[]): number {
6
+ let dot = 0, magA = 0, magB = 0
7
+ for (let i = 0; i < a.length; i++) {
8
+ dot += a[i] * b[i]
9
+ magA += a[i] * a[i]
10
+ magB += b[i] * b[i]
11
+ }
12
+ const denom = Math.sqrt(magA) * Math.sqrt(magB)
13
+ return denom === 0 ? 0 : dot / denom
14
+ }
15
+
16
+ function simpleEmbed(text: string): number[] {
17
+ const dim = 128
18
+ const vec = new Array(dim).fill(0)
19
+ for (let i = 0; i < text.length; i++) {
20
+ const idx = (text.charCodeAt(i) * 7 + i * 13) % dim
21
+ vec[idx] += 1 + (text.charCodeAt(i) % 5) * 0.1
22
+ }
23
+ const mag = Math.sqrt(vec.reduce((s, v) => s + v * v, 0))
24
+ return mag > 0 ? vec.map((v) => v / mag) : vec
25
+ }
26
+
27
+ interface SemanticEntry extends MemoryEntry {
28
+ embedding: number[]
29
+ }
30
+
31
+ export class SemanticMemory implements MemoryBackend {
32
+ readonly type = "semantic" as const
33
+ private entries: SemanticEntry[] = []
34
+
35
+ async store(entry: MemoryEntry): Promise<void> {
36
+ this.entries.push({
37
+ ...entry,
38
+ id: entry.id || generateId(),
39
+ timestamp: entry.timestamp || Date.now(),
40
+ embedding: simpleEmbed(entry.content),
41
+ })
42
+ }
43
+
44
+ async retrieve(query: MemoryQuery): Promise<MemoryEntry[]> {
45
+ let results = this.entries
46
+ if (query.key) results = results.filter((e) => e.key === query.key)
47
+ if (query.scope) results = results.filter((e) => e.scope === query.scope)
48
+ return results.slice(-(query.limit || 20))
49
+ }
50
+
51
+ async search(_query: string, limit = 10): Promise<MemorySearchResult[]> {
52
+ const queryEmbedding = simpleEmbed(_query)
53
+ const scored = this.entries
54
+ .map((entry) => ({
55
+ entry,
56
+ score: cosineSimilarity(queryEmbedding, entry.embedding),
57
+ }))
58
+ .sort((a, b) => b.score - a.score)
59
+ .slice(0, limit)
60
+ return scored
61
+ }
62
+
63
+ async delete(key: string): Promise<void> {
64
+ this.entries = this.entries.filter((e) => e.key !== key)
65
+ }
66
+
67
+ async clear(): Promise<void> {
68
+ this.entries = []
69
+ }
70
+ }
package/src/session.ts ADDED
@@ -0,0 +1,41 @@
1
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult } from "@lupaflow/types"
2
+ import type { MemoryBackend } from "./interface"
3
+ import { generateId } from "@lupaflow/core"
4
+
5
+ const SESSION_KEY = "__session__"
6
+
7
+ export class SessionMemory implements MemoryBackend {
8
+ readonly type = "session" as const
9
+ private entries: MemoryEntry[] = []
10
+
11
+ async store(entry: MemoryEntry): Promise<void> {
12
+ this.entries.push({
13
+ ...entry,
14
+ id: entry.id || generateId(),
15
+ scope: SESSION_KEY,
16
+ timestamp: entry.timestamp || Date.now(),
17
+ })
18
+ }
19
+
20
+ async retrieve(query: MemoryQuery): Promise<MemoryEntry[]> {
21
+ let results = this.entries.filter((e) => e.scope === SESSION_KEY)
22
+ if (query.key) results = results.filter((e) => e.key === query.key)
23
+ return results.slice(-(query.limit || 50))
24
+ }
25
+
26
+ async search(_query: string, limit = 10): Promise<MemorySearchResult[]> {
27
+ const q = _query.toLowerCase()
28
+ return this.entries
29
+ .filter((e) => e.scope === SESSION_KEY && e.content.toLowerCase().includes(q))
30
+ .slice(-limit)
31
+ .map((entry) => ({ entry, score: 1 }))
32
+ }
33
+
34
+ async delete(key: string): Promise<void> {
35
+ this.entries = this.entries.filter((e) => e.key !== key && e.scope === SESSION_KEY)
36
+ }
37
+
38
+ async clear(): Promise<void> {
39
+ this.entries = this.entries.filter((e) => e.scope !== SESSION_KEY)
40
+ }
41
+ }
@@ -0,0 +1,54 @@
1
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult } from "@lupaflow/types"
2
+ import type { MemoryBackend } from "./interface"
3
+ import { generateId } from "@lupaflow/core"
4
+
5
+ export class ShortTermMemory implements MemoryBackend {
6
+ readonly type = "short-term" as const
7
+ private entries: MemoryEntry[] = []
8
+ private maxEntries: number
9
+
10
+ constructor(maxEntries = 50) {
11
+ this.maxEntries = maxEntries
12
+ }
13
+
14
+ async store(entry: MemoryEntry): Promise<void> {
15
+ this.entries.push({
16
+ ...entry,
17
+ id: entry.id || generateId(),
18
+ timestamp: entry.timestamp || Date.now(),
19
+ })
20
+ if (this.entries.length > this.maxEntries) {
21
+ this.entries.shift()
22
+ }
23
+ }
24
+
25
+ async retrieve(query: MemoryQuery): Promise<MemoryEntry[]> {
26
+ let results = this.entries
27
+ if (query.key) {
28
+ results = results.filter((e) => e.key === query.key)
29
+ }
30
+ if (query.scope) {
31
+ results = results.filter((e) => e.scope === query.scope)
32
+ }
33
+ return results.slice(-(query.limit || 20))
34
+ }
35
+
36
+ async search(_query: string, limit = 5): Promise<MemorySearchResult[]> {
37
+ const q = _query.toLowerCase()
38
+ const results = this.entries
39
+ .filter((e) => e.content.toLowerCase().includes(q))
40
+ .slice(-limit)
41
+ return results.map((entry) => ({
42
+ entry,
43
+ score: 1,
44
+ }))
45
+ }
46
+
47
+ async delete(key: string): Promise<void> {
48
+ this.entries = this.entries.filter((e) => e.key !== key)
49
+ }
50
+
51
+ async clear(): Promise<void> {
52
+ this.entries = []
53
+ }
54
+ }
package/src/store.ts ADDED
@@ -0,0 +1,91 @@
1
+ import type { MemoryBackend } from "./interface"
2
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult, MemoryType } from "@lupaflow/types"
3
+ import { ShortTermMemory } from "./short-term"
4
+ import { LongTermMemory } from "./long-term"
5
+ import { SemanticMemory } from "./semantic"
6
+ import { SessionMemory } from "./session"
7
+ import { UserMemory } from "./user"
8
+ import { ProjectMemory } from "./project"
9
+ import { MemoryError } from "@lupaflow/core"
10
+ import { globalEventBus } from "@lupaflow/core"
11
+
12
+ export class MemoryStore {
13
+ private backends: Map<MemoryType, MemoryBackend> = new Map()
14
+
15
+ constructor(config?: { shortTerm?: boolean; longTerm?: boolean; semantic?: boolean; session?: boolean; user?: string; project?: boolean }) {
16
+ if (config?.shortTerm !== false) this.backends.set("short-term", new ShortTermMemory())
17
+ if (config?.longTerm) this.backends.set("long-term", new LongTermMemory())
18
+ if (config?.semantic) this.backends.set("semantic", new SemanticMemory())
19
+ if (config?.session !== false) this.backends.set("session", new SessionMemory())
20
+ if (config?.user) this.backends.set("user", new UserMemory(config.user))
21
+ if (config?.project) this.backends.set("project", new ProjectMemory())
22
+ }
23
+
24
+ register(type: MemoryType, backend: MemoryBackend): void {
25
+ this.backends.set(type, backend)
26
+ }
27
+
28
+ get(type: MemoryType): MemoryBackend {
29
+ const backend = this.backends.get(type)
30
+ if (!backend) throw new MemoryError(`No memory backend registered: ${type}`)
31
+ return backend
32
+ }
33
+
34
+ async store(entry: MemoryEntry): Promise<void> {
35
+ if (entry.type === "short-term" || entry.type === "session") {
36
+ const backend = this.backends.get(entry.type)
37
+ if (backend) {
38
+ await backend.store(entry)
39
+ await globalEventBus.emit("memory:update", {
40
+ type: entry.type,
41
+ key: entry.key,
42
+ } as any)
43
+ }
44
+ return
45
+ }
46
+
47
+ for (const backend of this.backends.values()) {
48
+ await backend.store({ ...entry })
49
+ }
50
+ await globalEventBus.emit("memory:update", { type: entry.type, key: entry.key } as any)
51
+ }
52
+
53
+ async retrieve(query: MemoryQuery): Promise<MemoryEntry[]> {
54
+ if (query.type) {
55
+ const backend = this.get(query.type)
56
+ const results = await backend.retrieve(query)
57
+ await globalEventBus.emit("memory:retrieve", {
58
+ type: query.type,
59
+ key: query.key || "all",
60
+ results: results.length,
61
+ } as any)
62
+ return results
63
+ }
64
+
65
+ const all: MemoryEntry[] = []
66
+ for (const backend of this.backends.values()) {
67
+ all.push(...(await backend.retrieve(query)))
68
+ }
69
+ return all
70
+ }
71
+
72
+ async search(query: string, limit = 10): Promise<MemorySearchResult[]> {
73
+ const all: MemorySearchResult[] = []
74
+ for (const backend of this.backends.values()) {
75
+ all.push(...(await backend.search(query, limit)))
76
+ }
77
+ return all.sort((a, b) => b.score - a.score).slice(0, limit)
78
+ }
79
+
80
+ async delete(key: string): Promise<void> {
81
+ for (const backend of this.backends.values()) {
82
+ await backend.delete(key)
83
+ }
84
+ }
85
+
86
+ async clear(): Promise<void> {
87
+ for (const backend of this.backends.values()) {
88
+ await backend.clear()
89
+ }
90
+ }
91
+ }
package/src/user.ts ADDED
@@ -0,0 +1,46 @@
1
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult } from "@lupaflow/types"
2
+ import type { MemoryBackend } from "./interface"
3
+ import { generateId } from "@lupaflow/core"
4
+
5
+ export class UserMemory implements MemoryBackend {
6
+ readonly type = "user" as const
7
+ private entries: MemoryEntry[] = []
8
+ private userId: string
9
+
10
+ constructor(userId: string) {
11
+ this.userId = userId
12
+ }
13
+
14
+ async store(entry: MemoryEntry): Promise<void> {
15
+ this.entries.push({
16
+ ...entry,
17
+ id: entry.id || generateId(),
18
+ scope: `user:${this.userId}`,
19
+ timestamp: entry.timestamp || Date.now(),
20
+ })
21
+ }
22
+
23
+ async retrieve(query: MemoryQuery): Promise<MemoryEntry[]> {
24
+ let results = this.entries.filter((e) => e.scope === `user:${this.userId}`)
25
+ if (query.key) results = results.filter((e) => e.key === query.key)
26
+ return results.slice(-(query.limit || 50))
27
+ }
28
+
29
+ async search(_query: string, limit = 10): Promise<MemorySearchResult[]> {
30
+ const q = _query.toLowerCase()
31
+ return this.entries
32
+ .filter((e) => e.scope === `user:${this.userId}` && e.content.toLowerCase().includes(q))
33
+ .slice(-limit)
34
+ .map((entry) => ({ entry, score: 1 }))
35
+ }
36
+
37
+ async delete(key: string): Promise<void> {
38
+ this.entries = this.entries.filter(
39
+ (e) => !(e.key === key && e.scope === `user:${this.userId}`)
40
+ )
41
+ }
42
+
43
+ async clear(): Promise<void> {
44
+ this.entries = this.entries.filter((e) => e.scope !== `user:${this.userId}`)
45
+ }
46
+ }
package/src/vector.ts ADDED
@@ -0,0 +1,73 @@
1
+ import type { MemoryEntry, MemoryQuery, MemorySearchResult } from "@lupaflow/types"
2
+ import type { MemoryBackend } from "./interface"
3
+ import { generateId } from "@lupaflow/core"
4
+
5
+ function cosineSimilarity(a: number[], b: number[]): number {
6
+ let dot = 0, magA = 0, magB = 0
7
+ for (let i = 0; i < a.length; i++) {
8
+ dot += a[i] * b[i]
9
+ magA += a[i] * a[i]
10
+ magB += b[i] * b[i]
11
+ }
12
+ const denom = Math.sqrt(magA) * Math.sqrt(magB)
13
+ return denom === 0 ? 0 : dot / denom
14
+ }
15
+
16
+ function generateEmbedding(text: string): number[] {
17
+ const dim = 384
18
+ const vec = new Array(dim).fill(0)
19
+ for (let i = 0; i < text.length; i++) {
20
+ const code = text.charCodeAt(i)
21
+ for (let j = 0; j < 5; j++) {
22
+ const idx = (code * (j + 1) + i * 17 + j * 31) % dim
23
+ vec[idx] += 1.0 / (j + 1)
24
+ }
25
+ }
26
+ const mag = Math.sqrt(vec.reduce((s, v) => s + v * v, 0))
27
+ return mag > 0 ? vec.map((v) => v / mag) : vec
28
+ }
29
+
30
+ interface VectorEntry extends MemoryEntry {
31
+ embedding: number[]
32
+ }
33
+
34
+ export class VectorMemory implements MemoryBackend {
35
+ readonly type = "vector" as const
36
+ private entries: VectorEntry[] = []
37
+
38
+ async store(entry: MemoryEntry): Promise<void> {
39
+ this.entries.push({
40
+ ...entry,
41
+ id: entry.id || generateId(),
42
+ timestamp: entry.timestamp || Date.now(),
43
+ embedding: generateEmbedding(entry.content),
44
+ })
45
+ }
46
+
47
+ async retrieve(query: MemoryQuery): Promise<MemoryEntry[]> {
48
+ let results: MemoryEntry[] = this.entries
49
+ if (query.key) results = results.filter((e) => e.key === query.key)
50
+ if (query.scope) results = results.filter((e) => e.scope === query.scope)
51
+ return results.slice(-(query.limit || 20))
52
+ }
53
+
54
+ async search(_query: string, limit = 10, threshold = 0.3): Promise<MemorySearchResult[]> {
55
+ const queryEmbedding = generateEmbedding(_query)
56
+ return this.entries
57
+ .map((entry) => ({
58
+ entry,
59
+ score: cosineSimilarity(queryEmbedding, entry.embedding),
60
+ }))
61
+ .filter((r) => r.score >= threshold)
62
+ .sort((a, b) => b.score - a.score)
63
+ .slice(0, limit)
64
+ }
65
+
66
+ async delete(key: string): Promise<void> {
67
+ this.entries = this.entries.filter((e) => e.key !== key)
68
+ }
69
+
70
+ async clear(): Promise<void> {
71
+ this.entries = []
72
+ }
73
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "./dist",
5
+ "rootDir": "./src"
6
+ },
7
+ "include": ["src"]
8
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,9 @@
1
+ import { defineConfig } from "tsup"
2
+ export default defineConfig({
3
+ entry: { index: "src/index.ts" },
4
+ format: ["cjs", "esm"],
5
+ dts: true,
6
+ sourcemap: true,
7
+ clean: true,
8
+ external: [/node_modules/],
9
+ })