@kernl-sdk/pg 0.1.10 → 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 +4 -5
  2. package/.turbo/turbo-check-types.log +36 -0
  3. package/CHANGELOG.md +41 -0
  4. package/README.md +124 -0
  5. package/dist/__tests__/integration.test.js +81 -1
  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 +62 -0
  106. package/dist/storage.d.ts.map +1 -1
  107. package/dist/storage.js +55 -10
  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 +7 -3
  112. package/dist/thread/store.d.ts.map +1 -1
  113. package/dist/thread/store.js +46 -105
  114. package/package.json +8 -5
  115. package/src/__tests__/integration.test.ts +114 -15
  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 +102 -11
  151. package/src/thread/sql.ts +159 -0
  152. package/src/thread/store.ts +58 -127
  153. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Convert QueryOptions to SQL codec inputs.
3
+ *
4
+ * pgvector constraints:
5
+ * - Only single vector signal supported (no multi-signal fusion)
6
+ * - No hybrid (vector + text) in same signal
7
+ * - Filter-only and orderBy-only queries are allowed
8
+ */
9
+ export function sqlize(query, config) {
10
+ const signals = query.query ?? query.max ?? [];
11
+ // Validate pgvector constraints
12
+ if (signals.length > 1) {
13
+ throw new Error("pgvector does not support multi-signal fusion. " +
14
+ "Use a single vector signal, or run multiple queries and fuse client-side.");
15
+ }
16
+ if (signals.length === 1) {
17
+ const { weight, ...fields } = signals[0];
18
+ let vectorCount = 0;
19
+ let textCount = 0;
20
+ for (const value of Object.values(fields)) {
21
+ if (value === undefined)
22
+ continue;
23
+ if (Array.isArray(value))
24
+ vectorCount++;
25
+ else if (typeof value === "string")
26
+ textCount++;
27
+ }
28
+ if (vectorCount > 1) {
29
+ throw new Error("pgvector does not support multi-vector fusion. " +
30
+ "Use a single vector signal, or run multiple queries and fuse client-side.");
31
+ }
32
+ if (vectorCount > 0 && textCount > 0) {
33
+ throw new Error("pgvector does not support hybrid (vector + text) fusion. " +
34
+ "Use a single vector signal, or run multiple queries and fuse client-side.");
35
+ }
36
+ }
37
+ return {
38
+ select: {
39
+ pkey: config.pkey,
40
+ signals,
41
+ binding: config.binding,
42
+ include: query.include,
43
+ },
44
+ where: { filter: query.filter },
45
+ order: {
46
+ signals,
47
+ orderBy: query.orderBy,
48
+ binding: config.binding,
49
+ schema: config.schema,
50
+ table: config.table,
51
+ },
52
+ limit: { topK: query.topK ?? 10, offset: query.offset ?? 0 },
53
+ };
54
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Schema conversion codecs for pgvector.
3
+ */
4
+ import type { Codec } from "@kernl-sdk/shared/lib";
5
+ import type { FieldSchema, VectorFieldSchema } from "@kernl-sdk/retrieval";
6
+ type Similarity = VectorFieldSchema["similarity"];
7
+ /**
8
+ * Codec for converting FieldSchema to Postgres column type.
9
+ */
10
+ export declare const FIELD_TYPE: Codec<FieldSchema, string>;
11
+ /**
12
+ * Codec for converting similarity metric to pgvector HNSW operator class.
13
+ */
14
+ export declare const SIMILARITY: Codec<Similarity | undefined, string>;
15
+ export {};
16
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../../src/pgvector/sql/schema.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EACV,WAAW,EACX,iBAAiB,EAElB,MAAM,sBAAsB,CAAC;AAG9B,KAAK,UAAU,GAAG,iBAAiB,CAAC,YAAY,CAAC,CAAC;AAgBlD;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,WAAW,EAAE,MAAM,CAUjD,CAAC;AAWF;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,SAAS,EAAE,MAAM,CAK5D,CAAC"}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Schema conversion codecs for pgvector.
3
+ */
4
+ /**
5
+ * Mapping from kernl scalar types to Postgres column types.
6
+ */
7
+ const SCALAR_TO_PG = {
8
+ string: "TEXT",
9
+ int: "INTEGER",
10
+ bigint: "BIGINT",
11
+ float: "DOUBLE PRECISION",
12
+ boolean: "BOOLEAN",
13
+ date: "TIMESTAMPTZ",
14
+ object: "JSONB",
15
+ geopoint: "POINT",
16
+ };
17
+ /**
18
+ * Codec for converting FieldSchema to Postgres column type.
19
+ */
20
+ export const FIELD_TYPE = {
21
+ encode: (schema) => {
22
+ if (schema.type === "vector") {
23
+ return `vector(${schema.dimensions})`;
24
+ }
25
+ return SCALAR_TO_PG[schema.type] ?? "TEXT";
26
+ },
27
+ decode: () => {
28
+ throw new Error("FIELD_TYPE.decode not implemented");
29
+ },
30
+ };
31
+ /**
32
+ * Mapping from similarity metric to pgvector operator class.
33
+ */
34
+ const SIMILARITY_TO_OPCLASS = {
35
+ cosine: "vector_cosine_ops",
36
+ euclidean: "vector_l2_ops",
37
+ dot_product: "vector_ip_ops",
38
+ };
39
+ /**
40
+ * Codec for converting similarity metric to pgvector HNSW operator class.
41
+ */
42
+ export const SIMILARITY = {
43
+ encode: (similarity) => SIMILARITY_TO_OPCLASS[similarity ?? "cosine"],
44
+ decode: () => {
45
+ throw new Error("SIMILARITY.decode not implemented");
46
+ },
47
+ };
@@ -0,0 +1,11 @@
1
+ import type { Codec } from "@kernl-sdk/shared/lib";
2
+ import type { SelectInput } from "./query.js";
3
+ export interface SQLClause {
4
+ sql: string;
5
+ params: unknown[];
6
+ }
7
+ /**
8
+ * Codec for building SELECT clause with score expression.
9
+ */
10
+ export declare const SQL_SELECT: Codec<SelectInput, SQLClause>;
11
+ //# sourceMappingURL=select.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"select.d.ts","sourceRoot":"","sources":["../../../src/pgvector/sql/select.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AACnD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AAWD;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,KAAK,CAAC,WAAW,EAAE,SAAS,CA+EpD,CAAC"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * pgvector distance operators by similarity metric.
3
+ */
4
+ const DISTANCE_OPS = {
5
+ cosine: "<=>",
6
+ euclidean: "<->",
7
+ dot_product: "<#>",
8
+ };
9
+ /**
10
+ * Codec for building SELECT clause with score expression.
11
+ */
12
+ export const SQL_SELECT = {
13
+ encode({ pkey, signals, binding, include }) {
14
+ const parts = [`"${pkey}" as id`];
15
+ const params = [];
16
+ // find vector signal for scoring
17
+ const vsig = signals.find((s) => {
18
+ for (const [key, val] of Object.entries(s)) {
19
+ if (key !== "weight" && Array.isArray(val))
20
+ return true;
21
+ }
22
+ return false;
23
+ });
24
+ if (vsig) {
25
+ // Extract vector field and value
26
+ let vecField = null;
27
+ let vecValue = null;
28
+ for (const [key, val] of Object.entries(vsig)) {
29
+ if (key !== "weight" && Array.isArray(val)) {
30
+ vecField = key;
31
+ vecValue = val;
32
+ break;
33
+ }
34
+ }
35
+ if (vecField && vecValue) {
36
+ const col = binding?.fields[vecField]?.column ?? vecField;
37
+ const similarity = binding?.fields[vecField]?.similarity ?? "cosine";
38
+ const op = DISTANCE_OPS[similarity];
39
+ params.push(JSON.stringify(vecValue));
40
+ // score expression varies by metric
41
+ switch (similarity) {
42
+ case "cosine":
43
+ parts.push(`1 - ("${col}" ${op} $1::vector) as score`);
44
+ break;
45
+ case "euclidean":
46
+ parts.push(`1 / (1 + ("${col}" ${op} $1::vector)) as score`);
47
+ break;
48
+ case "dot_product":
49
+ parts.push(`-("${col}" ${op} $1::vector) as score`);
50
+ break;
51
+ }
52
+ }
53
+ }
54
+ else {
55
+ parts.push("1 as score");
56
+ }
57
+ // Select columns based on include
58
+ // Note: We always select a calculated "score", so skip any document field named "score"
59
+ // to avoid ambiguity and ensure hit.score is always the similarity score.
60
+ if (binding && include !== false) {
61
+ const fields = Object.entries(binding.fields);
62
+ if (Array.isArray(include)) {
63
+ // Select only specified fields
64
+ for (const fieldName of include) {
65
+ if (fieldName === "score")
66
+ continue; // Skip to avoid overriding calculated score
67
+ const field = binding.fields[fieldName];
68
+ if (field) {
69
+ parts.push(`"${field.column}"`);
70
+ }
71
+ }
72
+ }
73
+ else {
74
+ // include: true or undefined → select all (except score)
75
+ for (const [fieldName, field] of fields) {
76
+ if (fieldName === "score")
77
+ continue; // Skip to avoid overriding calculated score
78
+ parts.push(`"${field.column}"`);
79
+ }
80
+ }
81
+ }
82
+ return { sql: parts.join(", "), params };
83
+ },
84
+ decode() {
85
+ throw new Error("SQL_SELECT.decode not implemented");
86
+ },
87
+ };
@@ -0,0 +1,8 @@
1
+ import type { Codec } from "@kernl-sdk/shared/lib";
2
+ import type { WhereInput } from "./query.js";
3
+ import type { SQLClause } from "./select.js";
4
+ /**
5
+ * Codec for building WHERE clause from MongoDB-style filter.
6
+ */
7
+ export declare const SQL_WHERE: Codec<WhereInput, SQLClause>;
8
+ //# sourceMappingURL=where.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"where.d.ts","sourceRoot":"","sources":["../../../src/pgvector/sql/where.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAEnD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAC1C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAE1C;;GAEG;AACH,eAAO,MAAM,SAAS,EAAE,KAAK,CAAC,UAAU,EAAE,SAAS,CAWlD,CAAC"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Codec for building WHERE clause from MongoDB-style filter.
3
+ */
4
+ export const SQL_WHERE = {
5
+ encode({ filter, startIdx }) {
6
+ if (!filter) {
7
+ return { sql: "", params: [] };
8
+ }
9
+ return encodeFilter(filter, startIdx);
10
+ },
11
+ decode() {
12
+ throw new Error("SQL_WHERE.decode not implemented");
13
+ },
14
+ };
15
+ /**
16
+ * Encode a filter to SQL (recursive).
17
+ */
18
+ function encodeFilter(filter, startIdx) {
19
+ const conditions = [];
20
+ const params = [];
21
+ let idx = startIdx;
22
+ for (const [key, value] of Object.entries(filter)) {
23
+ if (value === undefined)
24
+ continue;
25
+ // $and
26
+ if (key === "$and" && Array.isArray(value)) {
27
+ const parts = [];
28
+ for (const sub of value) {
29
+ const clause = encodeFilter(sub, idx);
30
+ if (clause.sql) {
31
+ parts.push(`(${clause.sql})`);
32
+ params.push(...clause.params);
33
+ idx += clause.params.length;
34
+ }
35
+ }
36
+ if (parts.length > 0) {
37
+ conditions.push(`(${parts.join(" AND ")})`);
38
+ }
39
+ continue;
40
+ }
41
+ // $or
42
+ if (key === "$or" && Array.isArray(value)) {
43
+ const parts = [];
44
+ for (const sub of value) {
45
+ const clause = encodeFilter(sub, idx);
46
+ if (clause.sql) {
47
+ parts.push(`(${clause.sql})`);
48
+ params.push(...clause.params);
49
+ idx += clause.params.length;
50
+ }
51
+ }
52
+ if (parts.length > 0) {
53
+ conditions.push(`(${parts.join(" OR ")})`);
54
+ }
55
+ continue;
56
+ }
57
+ // $not
58
+ if (key === "$not" && typeof value === "object" && !Array.isArray(value)) {
59
+ const clause = encodeFilter(value, idx);
60
+ if (clause.sql) {
61
+ conditions.push(`NOT (${clause.sql})`);
62
+ params.push(...clause.params);
63
+ idx += clause.params.length;
64
+ }
65
+ continue;
66
+ }
67
+ // field operators
68
+ if (isFieldOps(value)) {
69
+ const ops = value;
70
+ if (ops.$eq !== undefined) {
71
+ conditions.push(`"${key}" = $${idx++}`);
72
+ params.push(ops.$eq);
73
+ }
74
+ if (ops.$neq !== undefined) {
75
+ conditions.push(`"${key}" != $${idx++}`);
76
+ params.push(ops.$neq);
77
+ }
78
+ if (ops.$gt !== undefined) {
79
+ conditions.push(`"${key}" > $${idx++}`);
80
+ params.push(ops.$gt);
81
+ }
82
+ if (ops.$gte !== undefined) {
83
+ conditions.push(`"${key}" >= $${idx++}`);
84
+ params.push(ops.$gte);
85
+ }
86
+ if (ops.$lt !== undefined) {
87
+ conditions.push(`"${key}" < $${idx++}`);
88
+ params.push(ops.$lt);
89
+ }
90
+ if (ops.$lte !== undefined) {
91
+ conditions.push(`"${key}" <= $${idx++}`);
92
+ params.push(ops.$lte);
93
+ }
94
+ if (ops.$in !== undefined) {
95
+ conditions.push(`"${key}" = ANY($${idx++})`);
96
+ params.push(ops.$in);
97
+ }
98
+ if (ops.$nin !== undefined) {
99
+ conditions.push(`"${key}" != ALL($${idx++})`);
100
+ params.push(ops.$nin);
101
+ }
102
+ if (ops.$contains !== undefined) {
103
+ conditions.push(`"${key}" ILIKE $${idx++}`);
104
+ params.push(`%${ops.$contains}%`);
105
+ }
106
+ if (ops.$startsWith !== undefined) {
107
+ conditions.push(`"${key}" ILIKE $${idx++}`);
108
+ params.push(`${ops.$startsWith}%`);
109
+ }
110
+ if (ops.$endsWith !== undefined) {
111
+ conditions.push(`"${key}" ILIKE $${idx++}`);
112
+ params.push(`%${ops.$endsWith}`);
113
+ }
114
+ if (ops.$exists !== undefined) {
115
+ conditions.push(ops.$exists ? `"${key}" IS NOT NULL` : `"${key}" IS NULL`);
116
+ }
117
+ continue;
118
+ }
119
+ // Equality shorthand: { status: "active" }
120
+ if (value === null) {
121
+ conditions.push(`"${key}" IS NULL`);
122
+ }
123
+ else {
124
+ conditions.push(`"${key}" = $${idx++}`);
125
+ params.push(value);
126
+ }
127
+ }
128
+ return { sql: conditions.join(" AND "), params };
129
+ }
130
+ /**
131
+ * Check if value is an operator object (has $-prefixed keys).
132
+ */
133
+ function isFieldOps(value) {
134
+ if (typeof value !== "object" || value === null)
135
+ return false;
136
+ return Object.keys(value).some((k) => k.startsWith("$"));
137
+ }
@@ -0,0 +1,20 @@
1
+ import type { FieldSchema } from "@kernl-sdk/retrieval";
2
+ /**
3
+ * Mapping from a logical field name to a Postgres column.
4
+ */
5
+ export interface PGFieldBinding {
6
+ column: string;
7
+ type: FieldSchema["type"];
8
+ dimensions?: number;
9
+ similarity?: "cosine" | "euclidean" | "dot_product";
10
+ }
11
+ /**
12
+ * Configuration for binding a Postgres table as a search index.
13
+ */
14
+ export interface PGIndexConfig {
15
+ schema: string;
16
+ table: string;
17
+ pkey: string;
18
+ fields: Record<string, PGFieldBinding>;
19
+ }
20
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pgvector/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC;CACrD;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACxC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * pgvector utility functions.
3
+ */
4
+ /**
5
+ * Parse index id into schema and table.
6
+ *
7
+ * - "docs" → { schema: "public", table: "docs" }
8
+ * - "analytics.events" → { schema: "analytics", table: "events" }
9
+ */
10
+ export declare function parseIndexId(id: string): {
11
+ schema: string;
12
+ table: string;
13
+ };
14
+ /**
15
+ * Check if a value is a numeric array (vector).
16
+ */
17
+ export declare function isVector(val: unknown): val is number[];
18
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/pgvector/utils.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAM1E;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,MAAM,EAAE,CAEtD"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * pgvector utility functions.
3
+ */
4
+ /**
5
+ * Parse index id into schema and table.
6
+ *
7
+ * - "docs" → { schema: "public", table: "docs" }
8
+ * - "analytics.events" → { schema: "analytics", table: "events" }
9
+ */
10
+ export function parseIndexId(id) {
11
+ const parts = id.split(".");
12
+ if (parts.length === 2) {
13
+ return { schema: parts[0], table: parts[1] };
14
+ }
15
+ return { schema: "public", table: id };
16
+ }
17
+ /**
18
+ * Check if a value is a numeric array (vector).
19
+ */
20
+ export function isVector(val) {
21
+ return Array.isArray(val) && val.length > 0 && typeof val[0] === "number";
22
+ }
@@ -1,9 +1,19 @@
1
1
  import { Pool } from "pg";
2
2
  import type { KernlStorage } from "kernl";
3
+ import { type PGVectorConfig } from "./storage.js";
4
+ import { PGSearchIndex } from "./pgvector/search.js";
3
5
  /**
4
- * PostgreSQL connection configuration.
6
+ * Create a PostgreSQL storage adapter for Kernl.
5
7
  */
6
- export type PostgresConfig = {
8
+ export declare function postgres(config: PostgresConfig): KernlStorage;
9
+ /**
10
+ * Create a pgvector-backed search index.
11
+ */
12
+ export declare function pgvector(config: ConnectionConfig): PGSearchIndex;
13
+ /**
14
+ * Connection options for PostgreSQL.
15
+ */
16
+ export type ConnectionConfig = {
7
17
  pool: Pool;
8
18
  } | {
9
19
  connstr: string;
@@ -15,29 +25,12 @@ export type PostgresConfig = {
15
25
  password: string;
16
26
  };
17
27
  /**
18
- * Create a PostgreSQL storage adapter for Kernl.
19
- *
20
- * @param config - Connection configuration (pool, connection string, or credentials)
21
- * @returns KernlStorage instance backed by PostgreSQL
22
- *
23
- * @example
24
- * ```ts
25
- * // with connection string
26
- * const storage = postgres({ connstr: "postgresql://localhost/mydb" });
27
- *
28
- * // with connection options
29
- * const storage = postgres({
30
- * host: "localhost",
31
- * port: 5432,
32
- * database: "mydb",
33
- * user: "user",
34
- * password: "password"
35
- * });
36
- *
37
- * // existing pool
38
- * const pool = new Pool({ ... });
39
- * const storage = postgres({ pool });
40
- * ```
28
+ * PostgreSQL storage configuration.
41
29
  */
42
- export declare function postgres(config: PostgresConfig): KernlStorage;
30
+ export type PostgresConfig = ConnectionConfig & {
31
+ /**
32
+ * Enable pgvector support for semantic search.
33
+ */
34
+ vector?: boolean | PGVectorConfig;
35
+ };
43
36
  //# sourceMappingURL=postgres.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAI1C;;GAEG;AACH,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GACd;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEN;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,YAAY,CAkB7D"}
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../src/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,IAAI,CAAC;AAC1B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAE1C,OAAO,EAAa,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,YAAY,CAG7D;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,iBAGhD;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GACxB;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,GACd;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IACE,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB,CAAC;AAEN;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG,gBAAgB,GAAG;IAC9C;;OAEG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;CACnC,CAAC"}
package/dist/postgres.js CHANGED
@@ -1,40 +1,29 @@
1
1
  import { Pool } from "pg";
2
2
  import { PGStorage } from "./storage.js";
3
+ import { PGSearchIndex } from "./pgvector/search.js";
3
4
  /**
4
5
  * Create a PostgreSQL storage adapter for Kernl.
5
- *
6
- * @param config - Connection configuration (pool, connection string, or credentials)
7
- * @returns KernlStorage instance backed by PostgreSQL
8
- *
9
- * @example
10
- * ```ts
11
- * // with connection string
12
- * const storage = postgres({ connstr: "postgresql://localhost/mydb" });
13
- *
14
- * // with connection options
15
- * const storage = postgres({
16
- * host: "localhost",
17
- * port: 5432,
18
- * database: "mydb",
19
- * user: "user",
20
- * password: "password"
21
- * });
22
- *
23
- * // existing pool
24
- * const pool = new Pool({ ... });
25
- * const storage = postgres({ pool });
26
- * ```
27
6
  */
28
7
  export function postgres(config) {
29
- let pool;
8
+ const p = pool(config);
9
+ return new PGStorage({ pool: p, vector: config.vector });
10
+ }
11
+ /**
12
+ * Create a pgvector-backed search index.
13
+ */
14
+ export function pgvector(config) {
15
+ const p = pool(config);
16
+ return new PGSearchIndex({ pool: p });
17
+ }
18
+ function pool(config) {
30
19
  if ("pool" in config) {
31
- pool = config.pool;
20
+ return config.pool;
32
21
  }
33
22
  else if ("connstr" in config) {
34
- pool = new Pool({ connectionString: config.connstr });
23
+ return new Pool({ connectionString: config.connstr });
35
24
  }
36
25
  else {
37
- pool = new Pool({
26
+ return new Pool({
38
27
  host: config.host,
39
28
  port: config.port,
40
29
  database: config.database,
@@ -42,5 +31,4 @@ export function postgres(config) {
42
31
  password: config.password,
43
32
  });
44
33
  }
45
- return new PGStorage({ pool });
46
34
  }
package/dist/storage.d.ts CHANGED
@@ -1,6 +1,41 @@
1
1
  import type { Pool } from "pg";
2
2
  import type { AgentRegistry, ModelRegistry, KernlStorage, Transaction } from "kernl";
3
3
  import { PGThreadStore } from "./thread/store.js";
4
+ import { PGMemoryStore } from "./memory/store.js";
5
+ /**
6
+ * Vector similarity metric for pgvector.
7
+ */
8
+ export type VectorSimilarity = "cosine" | "euclidean" | "dot_product";
9
+ /**
10
+ * pgvector configuration options.
11
+ */
12
+ export interface PGVectorConfig {
13
+ /**
14
+ * Vector dimensions.
15
+ * @default 1536 (OpenAI text-embedding-3-small)
16
+ */
17
+ dimensions?: number;
18
+ /**
19
+ * Distance metric for similarity search.
20
+ * @default "cosine"
21
+ */
22
+ similarity?: VectorSimilarity;
23
+ }
24
+ /**
25
+ * Resolved vector configuration with defaults applied.
26
+ */
27
+ export interface ResolvedVectorConfig {
28
+ dimensions: number;
29
+ similarity: VectorSimilarity;
30
+ }
31
+ /**
32
+ * Default vector configuration.
33
+ */
34
+ export declare const DEFAULT_VECTOR_CONFIG: ResolvedVectorConfig;
35
+ /**
36
+ * Resolve vector config, applying defaults.
37
+ */
38
+ export declare function resolveVectorConfig(config: boolean | PGVectorConfig | undefined): ResolvedVectorConfig | undefined;
4
39
  /**
5
40
  * PostgreSQL storage configuration.
6
41
  */
@@ -9,14 +44,41 @@ export interface PGStorageConfig {
9
44
  * Pool instance for database connections.
10
45
  */
11
46
  pool: Pool;
47
+ /**
48
+ * Enable pgvector support for semantic search.
49
+ *
50
+ * - `true`: Use default config (1536 dimensions, cosine similarity)
51
+ * - `PGVectorConfig`: Custom dimensions and similarity metric
52
+ *
53
+ * Requires pgvector extension to be installed by superuser:
54
+ * ```sql
55
+ * CREATE EXTENSION IF NOT EXISTS vector;
56
+ * ```
57
+ */
58
+ vector?: boolean | PGVectorConfig;
12
59
  }
13
60
  /**
14
61
  * PostgreSQL storage adapter.
62
+ *
63
+ * Storage is lazily initialized on first use via `ensureInit()`. This means
64
+ * callers don't need to explicitly call `init()` - it happens automatically.
65
+ *
66
+ * NOTE: If the number of store methods grows significantly, consider replacing
67
+ * the manual `ensureInit()` calls with a Proxy-based wrapper for foolproof
68
+ * auto-initialization.
15
69
  */
16
70
  export declare class PGStorage implements KernlStorage {
17
71
  private pool;
72
+ private initPromise;
18
73
  threads: PGThreadStore;
74
+ memories: PGMemoryStore;
19
75
  constructor(config: PGStorageConfig);
76
+ /**
77
+ * Ensure storage is initialized before any operation.
78
+ *
79
+ * Safe to call multiple times - initialization only runs once.
80
+ */
81
+ private ensureInit;
20
82
  /**
21
83
  * Bind runtime registries to storage.
22
84
  */
@@ -1 +1 @@
1
- {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAK3C,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,YAAY,EACZ,WAAW,EACZ,MAAM,OAAO,CAAC;AAIf,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI/C;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;CACZ;AAED;;GAEG;AACH,qBAAa,SAAU,YAAW,YAAY;IAC5C,OAAO,CAAC,IAAI,CAAO;IAEnB,OAAO,EAAE,aAAa,CAAC;gBAEX,MAAM,EAAE,eAAe;IAKnC;;OAEG;IACH,IAAI,CAAC,UAAU,EAAE;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,GAAG,IAAI;IAIxE;;OAEG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIrE;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC9B;;OAEG;YACW,WAAW;IAUzB;;OAEG;YACW,YAAY;IA6F1B;;OAEG;YACW,UAAU;IAMxB;;OAEG;YACW,UAAU;IAIxB;;OAEG;YACW,SAAS;IAIvB;;OAEG;YACW,WAAW;CAkB1B"}
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAc,MAAM,IAAI,CAAC;AAG3C,OAAO,KAAK,EACV,aAAa,EACb,aAAa,EACb,YAAY,EACZ,WAAW,EACZ,MAAM,OAAO,CAAC;AAMf,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAC/C,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAI/C;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,WAAW,GAAG,aAAa,CAAC;AAEtE;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,gBAAgB,CAAC;CAC9B;AAED;;GAEG;AACH,eAAO,MAAM,qBAAqB,EAAE,oBAGnC,CAAC;AAEF;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,GAC3C,oBAAoB,GAAG,SAAS,CAOlC;AAED;;GAEG;AACH,MAAM,WAAW,eAAe;IAC9B;;OAEG;IACH,IAAI,EAAE,IAAI,CAAC;IAEX;;;;;;;;;;OAUG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,cAAc,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,qBAAa,SAAU,YAAW,YAAY;IAC5C,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,WAAW,CAA8B;IAEjD,OAAO,EAAE,aAAa,CAAC;IACvB,QAAQ,EAAE,aAAa,CAAC;gBAEZ,MAAM,EAAE,eAAe;IAMnC;;;;OAIG;YACW,UAAU;IAUxB;;OAEG;IACH,IAAI,CAAC,UAAU,EAAE;QAAE,MAAM,EAAE,aAAa,CAAC;QAAC,MAAM,EAAE,aAAa,CAAA;KAAE,GAAG,IAAI;IAIxE;;OAEG;IACG,WAAW,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAIrE;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAI5B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAyC9B;;OAEG;YACW,WAAW;IAUzB;;OAEG;YACW,YAAY;IA6F1B;;OAEG;YACW,UAAU;IAMxB;;OAEG;YACW,UAAU;IAIxB;;OAEG;YACW,SAAS;IAIvB;;OAEG;YACW,WAAW;CAkB1B"}