@kernl-sdk/pg 0.1.11 → 0.1.12

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 (153) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/.turbo/turbo-check-types.log +36 -0
  3. package/CHANGELOG.md +32 -0
  4. package/README.md +124 -0
  5. package/dist/__tests__/integration.test.js +2 -2
  6. package/dist/__tests__/memory-integration.test.d.ts +2 -0
  7. package/dist/__tests__/memory-integration.test.d.ts.map +1 -0
  8. package/dist/__tests__/memory-integration.test.js +287 -0
  9. package/dist/__tests__/memory.test.d.ts +2 -0
  10. package/dist/__tests__/memory.test.d.ts.map +1 -0
  11. package/dist/__tests__/memory.test.js +357 -0
  12. package/dist/index.d.ts +5 -3
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +5 -3
  15. package/dist/memory/sql.d.ts +30 -0
  16. package/dist/memory/sql.d.ts.map +1 -0
  17. package/dist/memory/sql.js +100 -0
  18. package/dist/memory/store.d.ts +41 -0
  19. package/dist/memory/store.d.ts.map +1 -0
  20. package/dist/memory/store.js +114 -0
  21. package/dist/migrations.d.ts +1 -1
  22. package/dist/migrations.d.ts.map +1 -1
  23. package/dist/migrations.js +9 -3
  24. package/dist/pgvector/__tests__/handle.test.d.ts +2 -0
  25. package/dist/pgvector/__tests__/handle.test.d.ts.map +1 -0
  26. package/dist/pgvector/__tests__/handle.test.js +277 -0
  27. package/dist/pgvector/__tests__/hit.test.d.ts +2 -0
  28. package/dist/pgvector/__tests__/hit.test.d.ts.map +1 -0
  29. package/dist/pgvector/__tests__/hit.test.js +134 -0
  30. package/dist/pgvector/__tests__/integration/document.integration.test.d.ts +7 -0
  31. package/dist/pgvector/__tests__/integration/document.integration.test.d.ts.map +1 -0
  32. package/dist/pgvector/__tests__/integration/document.integration.test.js +587 -0
  33. package/dist/pgvector/__tests__/integration/edge.integration.test.d.ts +8 -0
  34. package/dist/pgvector/__tests__/integration/edge.integration.test.d.ts.map +1 -0
  35. package/dist/pgvector/__tests__/integration/edge.integration.test.js +663 -0
  36. package/dist/pgvector/__tests__/integration/filters.integration.test.d.ts +8 -0
  37. package/dist/pgvector/__tests__/integration/filters.integration.test.d.ts.map +1 -0
  38. package/dist/pgvector/__tests__/integration/filters.integration.test.js +609 -0
  39. package/dist/pgvector/__tests__/integration/lifecycle.integration.test.d.ts +8 -0
  40. package/dist/pgvector/__tests__/integration/lifecycle.integration.test.d.ts.map +1 -0
  41. package/dist/pgvector/__tests__/integration/lifecycle.integration.test.js +449 -0
  42. package/dist/pgvector/__tests__/integration/query.integration.test.d.ts +8 -0
  43. package/dist/pgvector/__tests__/integration/query.integration.test.d.ts.map +1 -0
  44. package/dist/pgvector/__tests__/integration/query.integration.test.js +544 -0
  45. package/dist/pgvector/__tests__/search.test.d.ts +2 -0
  46. package/dist/pgvector/__tests__/search.test.d.ts.map +1 -0
  47. package/dist/pgvector/__tests__/search.test.js +279 -0
  48. package/dist/pgvector/handle.d.ts +60 -0
  49. package/dist/pgvector/handle.d.ts.map +1 -0
  50. package/dist/pgvector/handle.js +213 -0
  51. package/dist/pgvector/hit.d.ts +10 -0
  52. package/dist/pgvector/hit.d.ts.map +1 -0
  53. package/dist/pgvector/hit.js +44 -0
  54. package/dist/pgvector/index.d.ts +7 -0
  55. package/dist/pgvector/index.d.ts.map +1 -0
  56. package/dist/pgvector/index.js +5 -0
  57. package/dist/pgvector/search.d.ts +60 -0
  58. package/dist/pgvector/search.d.ts.map +1 -0
  59. package/dist/pgvector/search.js +227 -0
  60. package/dist/pgvector/sql/__tests__/limit.test.d.ts +2 -0
  61. package/dist/pgvector/sql/__tests__/limit.test.d.ts.map +1 -0
  62. package/dist/pgvector/sql/__tests__/limit.test.js +161 -0
  63. package/dist/pgvector/sql/__tests__/order.test.d.ts +2 -0
  64. package/dist/pgvector/sql/__tests__/order.test.d.ts.map +1 -0
  65. package/dist/pgvector/sql/__tests__/order.test.js +218 -0
  66. package/dist/pgvector/sql/__tests__/query.test.d.ts +2 -0
  67. package/dist/pgvector/sql/__tests__/query.test.d.ts.map +1 -0
  68. package/dist/pgvector/sql/__tests__/query.test.js +392 -0
  69. package/dist/pgvector/sql/__tests__/select.test.d.ts +2 -0
  70. package/dist/pgvector/sql/__tests__/select.test.d.ts.map +1 -0
  71. package/dist/pgvector/sql/__tests__/select.test.js +293 -0
  72. package/dist/pgvector/sql/__tests__/where.test.d.ts +2 -0
  73. package/dist/pgvector/sql/__tests__/where.test.d.ts.map +1 -0
  74. package/dist/pgvector/sql/__tests__/where.test.js +488 -0
  75. package/dist/pgvector/sql/index.d.ts +7 -0
  76. package/dist/pgvector/sql/index.d.ts.map +1 -0
  77. package/dist/pgvector/sql/index.js +6 -0
  78. package/dist/pgvector/sql/limit.d.ts +8 -0
  79. package/dist/pgvector/sql/limit.d.ts.map +1 -0
  80. package/dist/pgvector/sql/limit.js +20 -0
  81. package/dist/pgvector/sql/order.d.ts +9 -0
  82. package/dist/pgvector/sql/order.d.ts.map +1 -0
  83. package/dist/pgvector/sql/order.js +47 -0
  84. package/dist/pgvector/sql/query.d.ts +46 -0
  85. package/dist/pgvector/sql/query.d.ts.map +1 -0
  86. package/dist/pgvector/sql/query.js +54 -0
  87. package/dist/pgvector/sql/schema.d.ts +16 -0
  88. package/dist/pgvector/sql/schema.d.ts.map +1 -0
  89. package/dist/pgvector/sql/schema.js +47 -0
  90. package/dist/pgvector/sql/select.d.ts +11 -0
  91. package/dist/pgvector/sql/select.d.ts.map +1 -0
  92. package/dist/pgvector/sql/select.js +87 -0
  93. package/dist/pgvector/sql/where.d.ts +8 -0
  94. package/dist/pgvector/sql/where.d.ts.map +1 -0
  95. package/dist/pgvector/sql/where.js +137 -0
  96. package/dist/pgvector/types.d.ts +20 -0
  97. package/dist/pgvector/types.d.ts.map +1 -0
  98. package/dist/pgvector/types.js +1 -0
  99. package/dist/pgvector/utils.d.ts +18 -0
  100. package/dist/pgvector/utils.d.ts.map +1 -0
  101. package/dist/pgvector/utils.js +22 -0
  102. package/dist/postgres.d.ts +19 -26
  103. package/dist/postgres.d.ts.map +1 -1
  104. package/dist/postgres.js +15 -27
  105. package/dist/storage.d.ts +48 -0
  106. package/dist/storage.d.ts.map +1 -1
  107. package/dist/storage.js +32 -9
  108. package/dist/thread/sql.d.ts +38 -0
  109. package/dist/thread/sql.d.ts.map +1 -0
  110. package/dist/thread/sql.js +112 -0
  111. package/dist/thread/store.d.ts +2 -2
  112. package/dist/thread/store.d.ts.map +1 -1
  113. package/dist/thread/store.js +32 -102
  114. package/package.json +7 -4
  115. package/src/__tests__/integration.test.ts +15 -17
  116. package/src/__tests__/memory-integration.test.ts +355 -0
  117. package/src/__tests__/memory.test.ts +428 -0
  118. package/src/index.ts +19 -3
  119. package/src/memory/sql.ts +141 -0
  120. package/src/memory/store.ts +166 -0
  121. package/src/migrations.ts +13 -3
  122. package/src/pgvector/README.md +50 -0
  123. package/src/pgvector/__tests__/handle.test.ts +335 -0
  124. package/src/pgvector/__tests__/hit.test.ts +165 -0
  125. package/src/pgvector/__tests__/integration/document.integration.test.ts +717 -0
  126. package/src/pgvector/__tests__/integration/edge.integration.test.ts +835 -0
  127. package/src/pgvector/__tests__/integration/filters.integration.test.ts +721 -0
  128. package/src/pgvector/__tests__/integration/lifecycle.integration.test.ts +570 -0
  129. package/src/pgvector/__tests__/integration/query.integration.test.ts +667 -0
  130. package/src/pgvector/__tests__/search.test.ts +366 -0
  131. package/src/pgvector/handle.ts +285 -0
  132. package/src/pgvector/hit.ts +56 -0
  133. package/src/pgvector/index.ts +7 -0
  134. package/src/pgvector/search.ts +330 -0
  135. package/src/pgvector/sql/__tests__/limit.test.ts +180 -0
  136. package/src/pgvector/sql/__tests__/order.test.ts +248 -0
  137. package/src/pgvector/sql/__tests__/query.test.ts +548 -0
  138. package/src/pgvector/sql/__tests__/select.test.ts +367 -0
  139. package/src/pgvector/sql/__tests__/where.test.ts +554 -0
  140. package/src/pgvector/sql/index.ts +14 -0
  141. package/src/pgvector/sql/limit.ts +29 -0
  142. package/src/pgvector/sql/order.ts +55 -0
  143. package/src/pgvector/sql/query.ts +112 -0
  144. package/src/pgvector/sql/schema.ts +61 -0
  145. package/src/pgvector/sql/select.ts +100 -0
  146. package/src/pgvector/sql/where.ts +152 -0
  147. package/src/pgvector/types.ts +21 -0
  148. package/src/pgvector/utils.ts +24 -0
  149. package/src/postgres.ts +31 -33
  150. package/src/storage.ts +77 -9
  151. package/src/thread/sql.ts +159 -0
  152. package/src/thread/store.ts +40 -127
  153. package/tsconfig.tsbuildinfo +1 -0
@@ -17,7 +17,7 @@ export interface Migration {
17
17
  /**
18
18
  * List of all migrations in order.
19
19
  */
20
- export declare const migrations: Migration[];
20
+ export declare const MIGRATIONS: Migration[];
21
21
  /**
22
22
  * Minimum schema version required by this version of @kernl/pg.
23
23
  */
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAGxD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,EAQjC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,iBAAiB,CAAC"}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AACrC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAOxD;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,UAAU,CAAC;IACnB,WAAW,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9C;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,SAAS,EAcjC,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,uBAAuB,iBAAiB,CAAC"}
@@ -1,18 +1,24 @@
1
1
  /**
2
2
  * Database migrations.
3
3
  */
4
- import { TABLE_THREADS, TABLE_THREAD_EVENTS } from "@kernl-sdk/storage";
4
+ import { TABLE_THREADS, TABLE_THREAD_EVENTS, TABLE_MEMORIES, } from "@kernl-sdk/storage";
5
5
  /**
6
6
  * List of all migrations in order.
7
7
  */
8
- export const migrations = [
8
+ export const MIGRATIONS = [
9
9
  {
10
- id: "0001_initial",
10
+ id: "001_threads",
11
11
  async up(ctx) {
12
12
  await ctx.createTable(TABLE_THREADS);
13
13
  await ctx.createTable(TABLE_THREAD_EVENTS);
14
14
  },
15
15
  },
16
+ {
17
+ id: "002_memories",
18
+ async up(ctx) {
19
+ await ctx.createTable(TABLE_MEMORIES);
20
+ },
21
+ },
16
22
  ];
17
23
  /**
18
24
  * Minimum schema version required by this version of @kernl/pg.
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=handle.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handle.test.d.ts","sourceRoot":"","sources":["../../../src/pgvector/__tests__/handle.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,277 @@
1
+ import { describe, it, expect, beforeAll, afterAll, beforeEach } from "vitest";
2
+ import { Pool } from "pg";
3
+ import { PGSearchIndex } from "../search.js";
4
+ const TEST_DB_URL = process.env.KERNL_PG_TEST_URL;
5
+ const SCHEMA = "kernl_search_test";
6
+ describe.sequential("PGIndexHandle", () => {
7
+ if (!TEST_DB_URL) {
8
+ it.skip("requires KERNL_PG_TEST_URL environment variable", () => { });
9
+ return;
10
+ }
11
+ let pool;
12
+ let search;
13
+ let initialized = false;
14
+ const ensureInit = async () => {
15
+ if (initialized)
16
+ return;
17
+ await pool.query(`CREATE EXTENSION IF NOT EXISTS vector`);
18
+ await pool.query(`CREATE SCHEMA IF NOT EXISTS "${SCHEMA}"`);
19
+ await pool.query(`
20
+ CREATE TABLE IF NOT EXISTS "${SCHEMA}"."documents" (
21
+ id TEXT PRIMARY KEY,
22
+ title TEXT,
23
+ content TEXT,
24
+ status TEXT,
25
+ views INTEGER DEFAULT 0,
26
+ embedding vector(3)
27
+ )
28
+ `);
29
+ await pool.query(`
30
+ CREATE INDEX IF NOT EXISTS documents_embedding_idx
31
+ ON "${SCHEMA}"."documents"
32
+ USING hnsw (embedding vector_cosine_ops)
33
+ `);
34
+ initialized = true;
35
+ };
36
+ beforeAll(async () => {
37
+ pool = new Pool({ connectionString: TEST_DB_URL });
38
+ search = new PGSearchIndex({ pool, ensureInit });
39
+ // Clean slate
40
+ await pool.query(`DROP SCHEMA IF EXISTS "${SCHEMA}" CASCADE`);
41
+ });
42
+ afterAll(async () => {
43
+ await pool.query(`DROP SCHEMA IF EXISTS "${SCHEMA}" CASCADE`);
44
+ await pool.end();
45
+ });
46
+ beforeEach(async () => {
47
+ // Clear table between tests
48
+ if (initialized) {
49
+ await pool.query(`DELETE FROM "${SCHEMA}"."documents"`);
50
+ }
51
+ });
52
+ describe("bindIndex", () => {
53
+ it("registers binding for later use", async () => {
54
+ await search.bindIndex("docs", {
55
+ schema: SCHEMA,
56
+ table: "documents",
57
+ pkey: "id",
58
+ fields: {
59
+ title: { column: "title", type: "string" },
60
+ content: { column: "content", type: "string" },
61
+ embedding: {
62
+ column: "embedding",
63
+ type: "vector",
64
+ dimensions: 3,
65
+ similarity: "cosine",
66
+ },
67
+ },
68
+ });
69
+ // Can get handle without error
70
+ const handle = search.index("docs");
71
+ expect(handle.id).toBe("docs");
72
+ });
73
+ });
74
+ describe("index().query()", () => {
75
+ beforeAll(async () => {
76
+ await search.bindIndex("docs", {
77
+ schema: SCHEMA,
78
+ table: "documents",
79
+ pkey: "id",
80
+ fields: {
81
+ title: { column: "title", type: "string" },
82
+ content: { column: "content", type: "string" },
83
+ status: { column: "status", type: "string" },
84
+ views: { column: "views", type: "int" },
85
+ embedding: {
86
+ column: "embedding",
87
+ type: "vector",
88
+ dimensions: 3,
89
+ similarity: "cosine",
90
+ },
91
+ },
92
+ });
93
+ });
94
+ async function insertDocs() {
95
+ await ensureInit();
96
+ await pool.query(`
97
+ INSERT INTO "${SCHEMA}"."documents" (id, title, content, status, views, embedding)
98
+ VALUES
99
+ ('doc1', 'Hello World', 'First document', 'active', 100, '[0.1, 0.2, 0.3]'),
100
+ ('doc2', 'Goodbye World', 'Second document', 'active', 200, '[0.4, 0.5, 0.6]'),
101
+ ('doc3', 'Hello Again', 'Third document', 'draft', 50, '[0.15, 0.25, 0.35]'),
102
+ ('doc4', 'Final Doc', 'Fourth document', 'archived', 500, '[0.9, 0.8, 0.7]')
103
+ `);
104
+ }
105
+ it("queries with vector search", async () => {
106
+ await insertDocs();
107
+ const handle = search.index("docs");
108
+ const results = await handle.query({
109
+ query: [{ embedding: [0.1, 0.2, 0.3] }],
110
+ topK: 2,
111
+ });
112
+ expect(results).toHaveLength(2);
113
+ expect(results[0].id).toBe("doc1"); // closest match
114
+ expect(results[0].score).toBeGreaterThan(0.9); // high similarity
115
+ expect(results[0].index).toBe("docs");
116
+ });
117
+ it("queries with filter", async () => {
118
+ await insertDocs();
119
+ const handle = search.index("docs");
120
+ const results = await handle.query({
121
+ query: [{ embedding: [0.1, 0.2, 0.3] }],
122
+ filter: { status: "active" },
123
+ topK: 10,
124
+ });
125
+ expect(results).toHaveLength(2);
126
+ results.forEach((r) => {
127
+ expect(r.document?.status).toBe("active");
128
+ });
129
+ });
130
+ it("queries with comparison filter", async () => {
131
+ await insertDocs();
132
+ const handle = search.index("docs");
133
+ const results = await handle.query({
134
+ query: [{ embedding: [0.1, 0.2, 0.3] }],
135
+ filter: { views: { $gte: 100 } },
136
+ topK: 10,
137
+ });
138
+ expect(results.length).toBeGreaterThanOrEqual(2);
139
+ results.forEach((r) => {
140
+ expect(r.document?.views).toBeGreaterThanOrEqual(100);
141
+ });
142
+ });
143
+ it("queries with $or filter", async () => {
144
+ await insertDocs();
145
+ const handle = search.index("docs");
146
+ const results = await handle.query({
147
+ query: [{ embedding: [0.1, 0.2, 0.3] }],
148
+ filter: {
149
+ $or: [{ status: "draft" }, { status: "archived" }],
150
+ },
151
+ topK: 10,
152
+ });
153
+ expect(results).toHaveLength(2);
154
+ results.forEach((r) => {
155
+ expect(["draft", "archived"]).toContain(r.document?.status);
156
+ });
157
+ });
158
+ it("queries with $in filter", async () => {
159
+ await insertDocs();
160
+ const handle = search.index("docs");
161
+ const results = await handle.query({
162
+ query: [{ embedding: [0.1, 0.2, 0.3] }],
163
+ filter: { status: { $in: ["active", "draft"] } },
164
+ topK: 10,
165
+ });
166
+ expect(results).toHaveLength(3);
167
+ });
168
+ it("respects topK limit", async () => {
169
+ await insertDocs();
170
+ const handle = search.index("docs");
171
+ const results = await handle.query({
172
+ query: [{ embedding: [0.5, 0.5, 0.5] }],
173
+ topK: 2,
174
+ });
175
+ expect(results).toHaveLength(2);
176
+ });
177
+ it("respects offset for pagination", async () => {
178
+ await insertDocs();
179
+ const handle = search.index("docs");
180
+ // Get first 2
181
+ const page1 = await handle.query({
182
+ query: [{ embedding: [0.5, 0.5, 0.5] }],
183
+ topK: 2,
184
+ offset: 0,
185
+ });
186
+ // Get next 2
187
+ const page2 = await handle.query({
188
+ query: [{ embedding: [0.5, 0.5, 0.5] }],
189
+ topK: 2,
190
+ offset: 2,
191
+ });
192
+ expect(page1).toHaveLength(2);
193
+ expect(page2).toHaveLength(2);
194
+ expect(page1[0].id).not.toBe(page2[0].id);
195
+ });
196
+ it("queries with orderBy (non-vector)", async () => {
197
+ await insertDocs();
198
+ const handle = search.index("docs");
199
+ const results = await handle.query({
200
+ filter: { status: "active" },
201
+ orderBy: { field: "views", direction: "desc" },
202
+ topK: 10,
203
+ });
204
+ expect(results).toHaveLength(2);
205
+ expect(results[0].document?.views).toBe(200);
206
+ expect(results[1].document?.views).toBe(100);
207
+ });
208
+ it("returns documents with mapped field names", async () => {
209
+ await insertDocs();
210
+ const handle = search.index("docs");
211
+ const results = await handle.query({
212
+ query: [{ embedding: [0.1, 0.2, 0.3] }],
213
+ topK: 1,
214
+ });
215
+ expect(results[0].document).toHaveProperty("title");
216
+ expect(results[0].document).toHaveProperty("content");
217
+ expect(results[0].document).toHaveProperty("status");
218
+ expect(results[0].document).toHaveProperty("views");
219
+ expect(results[0].document).toHaveProperty("embedding");
220
+ });
221
+ it("returns empty array when no matches", async () => {
222
+ await insertDocs();
223
+ const handle = search.index("docs");
224
+ const results = await handle.query({
225
+ query: [{ embedding: [0.1, 0.2, 0.3] }],
226
+ filter: { status: "nonexistent" },
227
+ topK: 10,
228
+ });
229
+ expect(results).toEqual([]);
230
+ });
231
+ });
232
+ describe("index() generic type", () => {
233
+ beforeAll(async () => {
234
+ await search.bindIndex("typed-docs", {
235
+ schema: SCHEMA,
236
+ table: "documents",
237
+ pkey: "id",
238
+ fields: {
239
+ title: { column: "title", type: "string" },
240
+ content: { column: "content", type: "string" },
241
+ status: { column: "status", type: "string" },
242
+ views: { column: "views", type: "int" },
243
+ embedding: {
244
+ column: "embedding",
245
+ type: "vector",
246
+ dimensions: 3,
247
+ similarity: "cosine",
248
+ },
249
+ },
250
+ });
251
+ });
252
+ it("provides typed document access", async () => {
253
+ await ensureInit();
254
+ await pool.query(`
255
+ INSERT INTO "${SCHEMA}"."documents" (id, title, content, status, views, embedding)
256
+ VALUES ('typed1', 'Typed Doc', 'Typed content', 'active', 42, '[0.1, 0.2, 0.3]')
257
+ `);
258
+ const handle = search.index("typed-docs");
259
+ const results = await handle.query({
260
+ query: [{ embedding: [0.1, 0.2, 0.3] }],
261
+ topK: 1,
262
+ });
263
+ // TypeScript should allow these without errors
264
+ const doc = results[0].document;
265
+ expect(doc?.title).toBe("Typed Doc");
266
+ expect(doc?.views).toBe(42);
267
+ });
268
+ });
269
+ describe("error handling", () => {
270
+ it("throws when table does not exist", async () => {
271
+ const handle = search.index("unbound-index");
272
+ // Convention-based indexing tries to use "public.unbound-index"
273
+ // which doesn't exist, so PostgreSQL throws an error
274
+ await expect(handle.query({ query: [{ embedding: [0.1, 0.2, 0.3] }] })).rejects.toThrow('relation "public.unbound-index" does not exist');
275
+ });
276
+ });
277
+ });
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=hit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hit.test.d.ts","sourceRoot":"","sources":["../../../src/pgvector/__tests__/hit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,134 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { SEARCH_HIT } from "../hit.js";
3
+ describe("SEARCH_HIT", () => {
4
+ describe("decode", () => {
5
+ it("converts basic row to SearchHit", () => {
6
+ const row = {
7
+ id: "doc-123",
8
+ score: 0.95,
9
+ title: "Hello World",
10
+ content: "Some content",
11
+ };
12
+ const result = SEARCH_HIT.decode(row, "docs");
13
+ expect(result).toEqual({
14
+ id: "doc-123",
15
+ index: "docs",
16
+ score: 0.95,
17
+ document: {
18
+ id: "doc-123",
19
+ title: "Hello World",
20
+ content: "Some content",
21
+ },
22
+ });
23
+ });
24
+ it("converts id to string", () => {
25
+ const row = { id: 123, score: 0.5 };
26
+ const result = SEARCH_HIT.decode(row, "docs");
27
+ expect(result.id).toBe("123");
28
+ });
29
+ it("defaults score to 0 when not a number", () => {
30
+ const row = { id: "1", score: null };
31
+ const result = SEARCH_HIT.decode(row, "docs");
32
+ expect(result.score).toBe(0);
33
+ });
34
+ it("defaults score to 0 when undefined", () => {
35
+ const row = { id: "1" };
36
+ const result = SEARCH_HIT.decode(row, "docs");
37
+ expect(result.score).toBe(0);
38
+ });
39
+ it("returns document with only id when no other fields", () => {
40
+ const row = { id: "1", score: 0.5 };
41
+ const result = SEARCH_HIT.decode(row, "docs");
42
+ expect(result.document).toEqual({ id: "1" });
43
+ });
44
+ describe("with binding", () => {
45
+ const binding = {
46
+ schema: "public",
47
+ table: "documents",
48
+ pkey: "id",
49
+ fields: {
50
+ title: { column: "doc_title", type: "string" },
51
+ content: { column: "doc_content", type: "string" },
52
+ embedding: {
53
+ column: "vec",
54
+ type: "vector",
55
+ dimensions: 3,
56
+ },
57
+ },
58
+ };
59
+ it("maps columns to logical field names", () => {
60
+ const row = {
61
+ id: "1",
62
+ score: 0.9,
63
+ doc_title: "Hello",
64
+ doc_content: "World",
65
+ vec: [0.1, 0.2, 0.3],
66
+ };
67
+ const result = SEARCH_HIT.decode(row, "docs", binding);
68
+ expect(result.document).toEqual({
69
+ id: "1",
70
+ title: "Hello",
71
+ content: "World",
72
+ embedding: [0.1, 0.2, 0.3],
73
+ });
74
+ });
75
+ it("only includes fields defined in binding", () => {
76
+ const row = {
77
+ id: "1",
78
+ score: 0.9,
79
+ doc_title: "Hello",
80
+ extra_col: "should be ignored",
81
+ internal_field: "also ignored",
82
+ };
83
+ const result = SEARCH_HIT.decode(row, "docs", binding);
84
+ expect(result.document).toEqual({
85
+ id: "1",
86
+ title: "Hello",
87
+ });
88
+ expect(result.document?.extra_col).toBeUndefined();
89
+ expect(result.document?.internal_field).toBeUndefined();
90
+ });
91
+ it("handles missing columns gracefully", () => {
92
+ const row = {
93
+ id: "1",
94
+ score: 0.9,
95
+ doc_title: "Hello",
96
+ // doc_content and vec are missing
97
+ };
98
+ const result = SEARCH_HIT.decode(row, "docs", binding);
99
+ expect(result.document).toEqual({
100
+ id: "1",
101
+ title: "Hello",
102
+ });
103
+ });
104
+ it("returns document with only id when no bound fields present", () => {
105
+ const row = {
106
+ id: "1",
107
+ score: 0.9,
108
+ unrelated_column: "value",
109
+ };
110
+ const result = SEARCH_HIT.decode(row, "docs", binding);
111
+ expect(result.document).toEqual({ id: "1" });
112
+ });
113
+ });
114
+ describe("with TDocument generic", () => {
115
+ it("types document correctly", () => {
116
+ const row = {
117
+ id: "1",
118
+ score: 0.9,
119
+ title: "Hello",
120
+ views: 100,
121
+ };
122
+ const result = SEARCH_HIT.decode(row, "docs");
123
+ // TypeScript should allow accessing these fields
124
+ expect(result.document?.title).toBe("Hello");
125
+ expect(result.document?.views).toBe(100);
126
+ });
127
+ });
128
+ });
129
+ describe("encode", () => {
130
+ it("throws not implemented", () => {
131
+ expect(() => SEARCH_HIT.encode({})).toThrow("SEARCH_HIT.encode: not implemented");
132
+ });
133
+ });
134
+ });
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Document operations integration tests for pgvector.
3
+ *
4
+ * Tests upsert, patch, and delete operations against real PostgreSQL.
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=document.integration.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"document.integration.test.d.ts","sourceRoot":"","sources":["../../../../src/pgvector/__tests__/integration/document.integration.test.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}