@rekal/mem 0.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.
Files changed (80) hide show
  1. package/dist/db-BMh1OP4b.mjs +294 -0
  2. package/dist/doc-DnYN4jAU.mjs +116 -0
  3. package/dist/embed-rUMZxqed.mjs +100 -0
  4. package/dist/fs-DMp26Byo.mjs +32 -0
  5. package/dist/glob.d.mts +27 -0
  6. package/dist/glob.mjs +132 -0
  7. package/dist/index.d.mts +1465 -0
  8. package/dist/index.mjs +351 -0
  9. package/dist/llama-CT3dc9Cn.mjs +75 -0
  10. package/dist/models-DFQSgBNr.mjs +77 -0
  11. package/dist/openai-j2_2GM4J.mjs +76 -0
  12. package/dist/progress-B1JdNapX.mjs +263 -0
  13. package/dist/query-VFSpErTB.mjs +125 -0
  14. package/dist/runtime.node-DlQPaGrV.mjs +35 -0
  15. package/dist/search-BllHWtZF.mjs +166 -0
  16. package/dist/store-DE7S35SS.mjs +137 -0
  17. package/dist/transformers-CJ3QA2PK.mjs +55 -0
  18. package/dist/uri-CehXVDGB.mjs +28 -0
  19. package/dist/util-DNyrmcA3.mjs +11 -0
  20. package/dist/vfs-CNQbkhsf.mjs +222 -0
  21. package/foo.ts +3 -0
  22. package/foo2.ts +20 -0
  23. package/package.json +61 -0
  24. package/src/context.ts +77 -0
  25. package/src/db.ts +464 -0
  26. package/src/doc.ts +163 -0
  27. package/src/embed/base.ts +122 -0
  28. package/src/embed/index.ts +67 -0
  29. package/src/embed/llama.ts +111 -0
  30. package/src/embed/models.ts +104 -0
  31. package/src/embed/openai.ts +95 -0
  32. package/src/embed/transformers.ts +81 -0
  33. package/src/frecency.ts +58 -0
  34. package/src/fs.ts +36 -0
  35. package/src/glob.ts +163 -0
  36. package/src/index.ts +15 -0
  37. package/src/log.ts +60 -0
  38. package/src/md.ts +204 -0
  39. package/src/progress.ts +121 -0
  40. package/src/query.ts +131 -0
  41. package/src/runtime.bun.ts +33 -0
  42. package/src/runtime.node.ts +47 -0
  43. package/src/search.ts +230 -0
  44. package/src/snippet.ts +248 -0
  45. package/src/sqlite.ts +1 -0
  46. package/src/store.ts +180 -0
  47. package/src/uri.ts +28 -0
  48. package/src/util.ts +21 -0
  49. package/src/vfs.ts +257 -0
  50. package/test/doc.test.ts +61 -0
  51. package/test/fixtures/ignore-test/keep.md +0 -0
  52. package/test/fixtures/ignore-test/skip.log +0 -0
  53. package/test/fixtures/ignore-test/sub/keep.md +0 -0
  54. package/test/fixtures/store/agent/index.md +9 -0
  55. package/test/fixtures/store/agent/lessons.md +21 -0
  56. package/test/fixtures/store/agent/soul.md +28 -0
  57. package/test/fixtures/store/agent/tools.md +25 -0
  58. package/test/fixtures/store/concepts/frecency.md +30 -0
  59. package/test/fixtures/store/concepts/index.md +9 -0
  60. package/test/fixtures/store/concepts/memory-coherence.md +33 -0
  61. package/test/fixtures/store/concepts/rag.md +27 -0
  62. package/test/fixtures/store/index.md +9 -0
  63. package/test/fixtures/store/projects/index.md +9 -0
  64. package/test/fixtures/store/projects/rekall-inc/architecture.md +41 -0
  65. package/test/fixtures/store/projects/rekall-inc/decisions/index.md +9 -0
  66. package/test/fixtures/store/projects/rekall-inc/decisions/no-military.md +20 -0
  67. package/test/fixtures/store/projects/rekall-inc/index.md +28 -0
  68. package/test/fixtures/store/user/family.md +13 -0
  69. package/test/fixtures/store/user/index.md +9 -0
  70. package/test/fixtures/store/user/preferences.md +29 -0
  71. package/test/fixtures/store/user/profile.md +29 -0
  72. package/test/fs.test.ts +15 -0
  73. package/test/glob.test.ts +190 -0
  74. package/test/md.test.ts +177 -0
  75. package/test/query.test.ts +105 -0
  76. package/test/uri.test.ts +46 -0
  77. package/test/util.test.ts +62 -0
  78. package/test/vfs.test.ts +164 -0
  79. package/tsconfig.json +3 -0
  80. package/tsdown.config.ts +8 -0
@@ -0,0 +1,105 @@
1
+ // oxlint-disable sort-keys
2
+ import { describe, expect, test } from "vitest"
3
+ import { toFts, tokenize } from "../src/query.ts"
4
+
5
+ describe("tokenize", () => {
6
+ const cases: Record<string, ReturnType<typeof tokenize>> = {
7
+ "children family": [
8
+ { type: "term", value: "children" },
9
+ { type: "term", value: "family" },
10
+ ],
11
+ '"my children"': [{ type: "term", value: "my children" }],
12
+ "'my children'": [{ type: "term", value: "my children" }],
13
+ "-sports": [{ type: "term", value: "sports", neg: true }],
14
+ "children | kids": [
15
+ { type: "term", value: "children" },
16
+ { type: "op", value: "OR" },
17
+ { type: "term", value: "kids" },
18
+ ],
19
+ "(children | kids) family": [
20
+ { type: "paren", value: "(" },
21
+ { type: "term", value: "children" },
22
+ { type: "op", value: "OR" },
23
+ { type: "term", value: "kids" },
24
+ { type: "paren", value: ")" },
25
+ { type: "term", value: "family" },
26
+ ],
27
+ '"my children': [{ type: "term", value: "my children" }],
28
+ '""': [],
29
+ "what's up": [
30
+ { type: "term", value: "what's" },
31
+ { type: "term", value: "up" },
32
+ ],
33
+ "child*": [{ type: "term", value: "child*" }],
34
+ "entities:Lars": [{ type: "term", value: "Lars", field: "entities" }],
35
+ "tags:family children": [
36
+ { type: "term", value: "family", field: "tags" },
37
+ { type: "term", value: "children" },
38
+ ],
39
+ "-tags:sports": [{ type: "term", value: "sports", field: "tags", neg: true }],
40
+ "+children": [{ type: "term", value: "children", req: true }],
41
+ "+tags:family": [{ type: "term", value: "family", field: "tags", req: true }],
42
+ "unknown:foo": [{ type: "term", value: "unknown:foo" }],
43
+ 'children -sports "my family" | (kids age)': [
44
+ { type: "term", value: "children" },
45
+ { type: "term", value: "sports", neg: true },
46
+ { type: "term", value: "my family" },
47
+ { type: "op", value: "OR" },
48
+ { type: "paren", value: "(" },
49
+ { type: "term", value: "kids" },
50
+ { type: "term", value: "age" },
51
+ { type: "paren", value: ")" },
52
+ ],
53
+ }
54
+
55
+ for (const [input, expected] of Object.entries(cases)) {
56
+ test(input, () => expect(tokenize(input)).toEqual(expected))
57
+ }
58
+ })
59
+
60
+ describe("toFts", () => {
61
+ const or: Record<string, string> = {
62
+ "children family": '"children" OR "family"',
63
+ "children | kids": '"children" OR "kids"',
64
+ "children -sports": '"children" OR NOT "sports"',
65
+ '"my children"': '"my children"',
66
+ "'my children'": '"my children"',
67
+ "(children | kids) family": '("children" OR "kids") OR "family"',
68
+ "what's children?": '"what\'s" OR "children"',
69
+ "": "",
70
+ kids: '"kids"',
71
+ "child*": '"child"*',
72
+ "child* family": '"child"* OR "family"',
73
+ "child* famil*": '"child"* OR "famil"*',
74
+ "-sport*": 'NOT "sport"*',
75
+ "entities:Lars": 'entities : "Lars"',
76
+ "tags:family children": 'tags : "family" OR "children"',
77
+ "-tags:sports": 'NOT tags : "sports"',
78
+ "entities:Lars tags:family": 'entities : "Lars" OR tags : "family"',
79
+ "entities:Lars* tags:fam*": 'entities : "Lars"* OR tags : "fam"*',
80
+ "unknown:foo": '"unknown:foo"',
81
+ "folke +children": '"children" AND ("folke" OR "children")',
82
+ "+children folke": '"children" AND ("children" OR "folke")',
83
+ "folke +children +family": '"children" AND "family" AND ("folke" OR "children" OR "family")',
84
+ "+tags:family children": 'tags : "family" AND (tags : "family" OR "children")',
85
+ "+children": '"children"',
86
+ }
87
+
88
+ const and: Record<string, string> = {
89
+ "children family": '"children" AND "family"',
90
+ "children | kids": '"children" OR "kids"',
91
+ "children -sports": '"children" AND NOT "sports"',
92
+ "(children | kids) family": '("children" OR "kids") AND "family"',
93
+ "(children kids) family": '("children" AND "kids") AND "family"',
94
+ "child* family": '"child"* AND "family"',
95
+ "what's my name?": '"what\'s" AND "my" AND "name"',
96
+ }
97
+
98
+ for (const [input, expected] of Object.entries(or)) {
99
+ test(`OR: ${input}`, () => expect(toFts(input)).toBe(expected))
100
+ }
101
+
102
+ for (const [input, expected] of Object.entries(and)) {
103
+ test(`AND: ${input}`, () => expect(toFts(input, "AND")).toBe(expected))
104
+ }
105
+ })
@@ -0,0 +1,46 @@
1
+ // oxlint-disable sort-keys
2
+ import { describe, expect, test } from "vitest"
3
+ import { normUri, parentUri } from "../src/uri.ts"
4
+
5
+ describe("normUri", () => {
6
+ const cases: { input?: string; dir?: boolean; expected: string }[] = [
7
+ { expected: "rekal://" },
8
+ { input: "", expected: "rekal://" },
9
+ { input: " ", expected: "rekal://" },
10
+ { input: "foo", expected: "rekal://foo" },
11
+ { input: "foo/bar", expected: "rekal://foo/bar" },
12
+ { input: "foo/bar/index.md", expected: "rekal://foo/bar/" },
13
+ { input: "rekal://notes", expected: "rekal://notes" },
14
+ { input: "rekal://notes/", expected: "rekal://notes/" },
15
+ { input: "rekall://test", expected: "rekal://test" },
16
+ { input: "a//b///c", expected: "rekal://a/b/c" },
17
+ { input: "///a/b", expected: "rekal://a/b" },
18
+ { input: "a\\b\\c", expected: "rekal://a/b/c" },
19
+ { input: "foo", dir: true, expected: "rekal://foo/" },
20
+ { input: "foo/bar", dir: true, expected: "rekal://foo/bar/" },
21
+ { input: "foo/bar/", dir: true, expected: "rekal://foo/bar/" },
22
+ { input: "foo", dir: false, expected: "rekal://foo" },
23
+ { input: "foo/bar/", dir: false, expected: "rekal://foo/bar" },
24
+ ]
25
+
26
+ for (const { input, dir, expected } of cases) {
27
+ test(`normUri(${JSON.stringify(input)}, ${dir}) => ${expected}`, () =>
28
+ expect(normUri(input, dir)).toBe(expected))
29
+ }
30
+ })
31
+
32
+ describe("parentUri", () => {
33
+ const cases: { input: string; expected: string | undefined }[] = [
34
+ { input: "rekal://", expected: undefined },
35
+ { input: "rekal://foo", expected: "rekal://" },
36
+ { input: "rekal://foo/", expected: "rekal://" },
37
+ { input: "rekal://foo\\bar", expected: "rekal://foo/" },
38
+ { input: "rekal://foo/bar/", expected: "rekal://foo/" },
39
+ { input: "rekal://foo/bar/baz.md", expected: "rekal://foo/bar/" },
40
+ { input: "rekal://a/b/c/d", expected: "rekal://a/b/c/" },
41
+ ]
42
+
43
+ for (const { input, expected } of cases) {
44
+ test(`parentUri(${input}) => ${expected}`, () => expect(parentUri(input)).toBe(expected))
45
+ }
46
+ })
@@ -0,0 +1,62 @@
1
+ import { describe, expect, test } from "vitest"
2
+ import { parseFrontmatter } from "../src/md.ts"
3
+
4
+ describe("parseFrontmatter", () => {
5
+ test("parses yaml frontmatter", () => {
6
+ const { frontmatter, body } = parseFrontmatter(`---
7
+ summary: "hello world"
8
+ tags: [a, b]
9
+ ---
10
+
11
+ # Title
12
+
13
+ Body text.
14
+ `)
15
+ expect(frontmatter).toEqual({ summary: "hello world", tags: ["a", "b"] })
16
+ expect(body).toContain("# Title")
17
+ expect(body).toContain("Body text.")
18
+ })
19
+
20
+ test("returns empty data when no frontmatter", () => {
21
+ const { frontmatter, body } = parseFrontmatter("Just plain text.")
22
+ expect(frontmatter).toEqual({})
23
+ expect(body).toBe("Just plain text.")
24
+ })
25
+
26
+ test("handles missing closing delimiter gracefully", () => {
27
+ const { frontmatter, body } = parseFrontmatter(`---
28
+ summary: "oops"
29
+ No closing delimiter here.
30
+ `)
31
+ // Should not crash — returns empty frontmatter, full content as body
32
+ expect(frontmatter).toEqual({})
33
+ expect(body).toContain("summary")
34
+ })
35
+
36
+ test("handles empty frontmatter", () => {
37
+ const { frontmatter, body } = parseFrontmatter(`---
38
+ ---
39
+
40
+ Content after empty frontmatter.
41
+ `)
42
+ expect(frontmatter).toEqual({})
43
+ expect(body).toContain("Content after empty frontmatter.")
44
+ })
45
+
46
+ test("handles complex yaml values", () => {
47
+ const { frontmatter } = parseFrontmatter(`---
48
+ entities: [Folke, "Lars Lemaitre", snacks.nvim]
49
+ tags:
50
+ - user
51
+ - family
52
+ nested:
53
+ key: value
54
+ ---
55
+
56
+ Body.
57
+ `)
58
+ expect(frontmatter.entities).toEqual(["Folke", "Lars Lemaitre", "snacks.nvim"])
59
+ expect(frontmatter.tags).toEqual(["user", "family"])
60
+ expect(frontmatter.nested).toEqual({ key: "value" })
61
+ })
62
+ })
@@ -0,0 +1,164 @@
1
+ import type { VfsEntry } from "../src/vfs.ts"
2
+
3
+ import { fileURLToPath } from "node:url"
4
+ import { join } from "pathe"
5
+ import { describe, expect, test } from "vitest"
6
+ import { Context } from "../src/context.ts"
7
+ import { Vfs } from "../src/vfs.ts"
8
+
9
+ const FIXTURES = join(fileURLToPath(import.meta.url), "..", "fixtures/store")
10
+
11
+ function createVfs() {
12
+ const ctx = new Context({})
13
+ return new Vfs(ctx)
14
+ }
15
+
16
+ async function collect(vfs: Vfs, uri: string, depth?: number) {
17
+ const results: VfsEntry[] = []
18
+ for await (const item of vfs.find({ depth, uri })) {
19
+ results.push(item)
20
+ }
21
+ return results
22
+ }
23
+
24
+ describe("Vfs addFolder", () => {
25
+ test("adds a folder to the VFS", () => {
26
+ const vfs = createVfs()
27
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://notes" })
28
+ expect(vfs.folders).toHaveLength(1)
29
+ expect(vfs.folders[0].uri).toBe("rekal://notes/")
30
+ })
31
+
32
+ test("normalizes trailing slash on URI", () => {
33
+ const vfs = createVfs()
34
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://notes" })
35
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://docs/" })
36
+ expect(vfs.folders[0].uri).toBe("rekal://notes/")
37
+ expect(vfs.folders[1].uri).toBe("rekal://docs/")
38
+ })
39
+ })
40
+
41
+ describe("Vfs find", () => {
42
+ test("finds files in a single mount", async () => {
43
+ const vfs = createVfs()
44
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://store" })
45
+ const results = await collect(vfs, "rekal://store")
46
+ expect(results.length).toBeGreaterThan(0)
47
+
48
+ const uris = results.map((r) => r.uri)
49
+ expect(uris.some((u) => u.includes("family.md"))).toBe(true)
50
+ expect(uris.some((u) => u.includes("user/"))).toBe(true)
51
+ })
52
+
53
+ test("depth=1 shows only immediate children", async () => {
54
+ const vfs = createVfs()
55
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://store" })
56
+ const results = await collect(vfs, "rekal://store", 1)
57
+ const uris = results.map((r) => r.uri)
58
+
59
+ // Should see top-level dirs (index.md is filtered as a dir node)
60
+ expect(uris.some((u) => u === "rekal://store/user/")).toBe(true)
61
+ expect(uris.some((u) => u === "rekal://store/projects/")).toBe(true)
62
+
63
+ // Should NOT see nested files
64
+ expect(uris.some((u) => u.includes("family.md"))).toBe(false)
65
+ })
66
+
67
+ test("nested mount appears as virtual directory", async () => {
68
+ const vfs = createVfs()
69
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://notes" })
70
+ vfs.addFolder({ path: join(FIXTURES, "concepts"), uri: "rekal://notes/extra" })
71
+ const results = await collect(vfs, "rekal://notes", 1)
72
+ const uris = results.map((r) => r.uri)
73
+
74
+ // The nested mount should appear as a directory
75
+ expect(uris).toContain("rekal://notes/extra/")
76
+ })
77
+
78
+ test("nested mount has correct path", async () => {
79
+ const vfs = createVfs()
80
+ const toolsDir = join(FIXTURES, "concepts")
81
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://notes" })
82
+ vfs.addFolder({ path: toolsDir, uri: "rekal://notes/extra" })
83
+ const results = await collect(vfs, "rekal://notes", 1)
84
+
85
+ const extra = results.find((r) => r.uri === "rekal://notes/extra/")
86
+ expect(extra).toBeDefined()
87
+ // Should have a path from the explicit mount
88
+ expect(extra!.path.startsWith(toolsDir)).toBe(true)
89
+ })
90
+
91
+ test("nested mount contents are found at depth=2", async () => {
92
+ const vfs = createVfs()
93
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://notes" })
94
+ vfs.addFolder({ path: join(FIXTURES, "concepts"), uri: "rekal://notes/extra" })
95
+ const results = await collect(vfs, "rekal://notes", 2)
96
+ const uris = results.map((r) => r.uri)
97
+
98
+ // Should find files inside the nested mount
99
+ expect(uris.some((u) => u.includes("extra/") && u.endsWith(".md"))).toBe(true)
100
+ })
101
+
102
+ test("find below a mount resolves correctly", async () => {
103
+ const vfs = createVfs()
104
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://store" })
105
+ const results = await collect(vfs, "rekal://store/user")
106
+ const uris = results.map((r) => r.uri)
107
+
108
+ expect(uris.some((u) => u.includes("family.md"))).toBe(true)
109
+ expect(uris.some((u) => u.includes("preferences.md"))).toBe(true)
110
+ // Should NOT include files from other top-level dirs
111
+ expect(uris.some((u) => u.includes("concepts/"))).toBe(false)
112
+ })
113
+
114
+ test("multiple paths for same URI prefix", async () => {
115
+ const vfs = createVfs()
116
+ vfs.addFolder({ path: join(FIXTURES, "user"), uri: "rekal://merged" })
117
+ vfs.addFolder({ path: join(FIXTURES, "concepts"), uri: "rekal://merged" })
118
+ const results = await collect(vfs, "rekal://merged")
119
+ const uris = results.map((r) => r.uri)
120
+
121
+ // Should find files from both directories
122
+ expect(uris.some((u) => u.includes("family.md"))).toBe(true) // from user
123
+ expect(uris.some((u) => u.includes("frecency.md"))).toBe(true) // from concepts
124
+ })
125
+
126
+ test("deduplicates URIs from overlapping mounts", async () => {
127
+ const vfs = createVfs()
128
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://notes" })
129
+ // Mount the user subdir again explicitly
130
+ vfs.addFolder({ path: join(FIXTURES, "user"), uri: "rekal://notes/user" })
131
+ const results = await collect(vfs, "rekal://notes")
132
+ const uris = results.map((r) => r.uri)
133
+
134
+ // family.md should appear only once
135
+ const familyResults = uris.filter((u) => u === "rekal://notes/user/family.md")
136
+ expect(familyResults).toHaveLength(1)
137
+ })
138
+
139
+ test("overlapping mount yields results from both sources", async () => {
140
+ const vfs = createVfs()
141
+ const userDir = join(FIXTURES, "user")
142
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://notes" })
143
+ vfs.addFolder({ path: userDir, uri: "rekal://notes/user" })
144
+ const results = await collect(vfs, "rekal://notes")
145
+ const uris = results.map((r) => r.uri)
146
+
147
+ // Should find the user/ directory and files from both mounts
148
+ expect(uris.some((u) => u === "rekal://notes/user/")).toBe(true)
149
+ expect(uris.some((u) => u.includes("family.md"))).toBe(true)
150
+ })
151
+ })
152
+
153
+ describe("Vfs ls", () => {
154
+ test("ls is equivalent to find with depth=1", async () => {
155
+ const vfs = createVfs()
156
+ vfs.addFolder({ path: FIXTURES, uri: "rekal://store" })
157
+ const lsResults = await collect(vfs, "rekal://store", 1)
158
+ const findResults: VfsEntry[] = []
159
+ for await (const item of vfs.ls({ uri: "rekal://store" })) {
160
+ findResults.push(item)
161
+ }
162
+ expect(lsResults.map((r) => r.uri).toSorted()).toEqual(findResults.map((r) => r.uri).toSorted())
163
+ })
164
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,3 @@
1
+ {
2
+ "extends": "../../tsconfig.json"
3
+ }
@@ -0,0 +1,8 @@
1
+ import { defineConfig } from "tsdown"
2
+
3
+ export default defineConfig({
4
+ entry: {
5
+ glob: "src/glob.ts",
6
+ index: "src/index.ts",
7
+ },
8
+ })