@pgsage/core 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.
Files changed (70) hide show
  1. package/README.md +126 -0
  2. package/dist/corrector/index.d.ts +72 -0
  3. package/dist/corrector/index.d.ts.map +1 -0
  4. package/dist/corrector/index.js +113 -0
  5. package/dist/corrector/index.js.map +1 -0
  6. package/dist/db/index.d.ts +3 -0
  7. package/dist/db/index.d.ts.map +1 -0
  8. package/dist/db/index.js +2 -0
  9. package/dist/db/index.js.map +1 -0
  10. package/dist/db/pool.d.ts +46 -0
  11. package/dist/db/pool.d.ts.map +1 -0
  12. package/dist/db/pool.js +46 -0
  13. package/dist/db/pool.js.map +1 -0
  14. package/dist/embeddings/index.d.ts +4 -0
  15. package/dist/embeddings/index.d.ts.map +1 -0
  16. package/dist/embeddings/index.js +2 -0
  17. package/dist/embeddings/index.js.map +1 -0
  18. package/dist/embeddings/types.d.ts +39 -0
  19. package/dist/embeddings/types.d.ts.map +1 -0
  20. package/dist/embeddings/types.js +16 -0
  21. package/dist/embeddings/types.js.map +1 -0
  22. package/dist/embeddings/voyage.d.ts +70 -0
  23. package/dist/embeddings/voyage.d.ts.map +1 -0
  24. package/dist/embeddings/voyage.js +163 -0
  25. package/dist/embeddings/voyage.js.map +1 -0
  26. package/dist/estimator/index.d.ts +53 -0
  27. package/dist/estimator/index.d.ts.map +1 -0
  28. package/dist/estimator/index.js +57 -0
  29. package/dist/estimator/index.js.map +1 -0
  30. package/dist/executor/index.d.ts +56 -0
  31. package/dist/executor/index.d.ts.map +1 -0
  32. package/dist/executor/index.js +86 -0
  33. package/dist/executor/index.js.map +1 -0
  34. package/dist/explainer/index.d.ts +39 -0
  35. package/dist/explainer/index.d.ts.map +1 -0
  36. package/dist/explainer/index.js +79 -0
  37. package/dist/explainer/index.js.map +1 -0
  38. package/dist/explainer/prompt.d.ts +37 -0
  39. package/dist/explainer/prompt.d.ts.map +1 -0
  40. package/dist/explainer/prompt.js +102 -0
  41. package/dist/explainer/prompt.js.map +1 -0
  42. package/dist/index.d.ts +19 -0
  43. package/dist/index.d.ts.map +1 -0
  44. package/dist/index.js +24 -0
  45. package/dist/index.js.map +1 -0
  46. package/dist/introspector/index.d.ts +127 -0
  47. package/dist/introspector/index.d.ts.map +1 -0
  48. package/dist/introspector/index.js +460 -0
  49. package/dist/introspector/index.js.map +1 -0
  50. package/dist/orchestrator/index.d.ts +113 -0
  51. package/dist/orchestrator/index.d.ts.map +1 -0
  52. package/dist/orchestrator/index.js +126 -0
  53. package/dist/orchestrator/index.js.map +1 -0
  54. package/dist/planner/index.d.ts +83 -0
  55. package/dist/planner/index.d.ts.map +1 -0
  56. package/dist/planner/index.js +67 -0
  57. package/dist/planner/index.js.map +1 -0
  58. package/dist/planner/prompt.d.ts +37 -0
  59. package/dist/planner/prompt.d.ts.map +1 -0
  60. package/dist/planner/prompt.js +436 -0
  61. package/dist/planner/prompt.js.map +1 -0
  62. package/dist/retriever/index.d.ts +90 -0
  63. package/dist/retriever/index.d.ts.map +1 -0
  64. package/dist/retriever/index.js +164 -0
  65. package/dist/retriever/index.js.map +1 -0
  66. package/dist/validator/index.d.ts +26 -0
  67. package/dist/validator/index.d.ts.map +1 -0
  68. package/dist/validator/index.js +205 -0
  69. package/dist/validator/index.js.map +1 -0
  70. package/package.json +63 -0
@@ -0,0 +1,164 @@
1
+ /**
2
+ * Retriever
3
+ *
4
+ * Two responsibilities:
5
+ *
6
+ * 1. indexSchema() — converts an IntrospectedSchema into text descriptions,
7
+ * embeds them via an EmbeddingProvider, and upserts the results into the
8
+ * schema_embeddings table. Safe to run repeatedly (idempotent via ON
9
+ * CONFLICT ... DO UPDATE).
10
+ *
11
+ * 2. retrieve() — embeds a natural-language question and performs a pgvector
12
+ * cosine similarity search against schema_embeddings, returning the
13
+ * top-K most relevant schema chunks.
14
+ */
15
+ // ---------------------------------------------------------------------------
16
+ // Text description generation
17
+ // ---------------------------------------------------------------------------
18
+ /**
19
+ * Generate a human-readable description of a table suitable for embedding.
20
+ * Includes: schema/table name, type, row estimate, comment, and a column
21
+ * summary with types, nullability, PK/FK annotations.
22
+ */
23
+ export function describeTable(table) {
24
+ const type = table.tableType === "view" ? "VIEW" : "TABLE";
25
+ const rowHint = table.estimatedRowCount >= 0
26
+ ? `, ~${table.estimatedRowCount.toLocaleString()} rows`
27
+ : "";
28
+ const header = `${type} ${table.schema}.${table.name}${rowHint}`;
29
+ const comment = table.comment ? ` — ${table.comment}` : "";
30
+ const colSummary = table.columns
31
+ .map((c) => {
32
+ const parts = [c.name, c.dataType];
33
+ if (!c.isNullable)
34
+ parts.push("NOT NULL");
35
+ if (c.isPrimaryKey)
36
+ parts.push("PK");
37
+ const fk = table.foreignKeys.find((fk) => fk.columns.includes(c.name));
38
+ if (fk)
39
+ parts.push(`FK→${fk.referencedSchema}.${fk.referencedTable}.${fk.referencedColumns[0]}`);
40
+ if (c.comment)
41
+ parts.push(`(${c.comment})`);
42
+ return parts.join(" ");
43
+ })
44
+ .join(", ");
45
+ return `${header}${comment}. Columns: ${colSummary}.`;
46
+ }
47
+ /**
48
+ * Generate a human-readable description of a single column suitable for
49
+ * embedding. Includes table context, type info, PK/FK annotations, and
50
+ * comment.
51
+ */
52
+ export function describeColumn(table, columnIndex) {
53
+ const col = table.columns[columnIndex];
54
+ const nullable = col.isNullable ? "nullable" : "NOT NULL";
55
+ const pkNote = col.isPrimaryKey ? " PRIMARY KEY" : "";
56
+ const fk = table.foreignKeys.find((fk) => fk.columns.includes(col.name));
57
+ const fkNote = fk
58
+ ? ` References ${fk.referencedSchema}.${fk.referencedTable}(${fk.referencedColumns[0]}).`
59
+ : "";
60
+ const commentNote = col.comment ? ` ${col.comment}.` : "";
61
+ const defaultNote = col.defaultValue && !col.defaultValue.startsWith("nextval")
62
+ ? ` Default: ${col.defaultValue}.`
63
+ : "";
64
+ return (`Column ${table.schema}.${table.name}.${col.name}: ` +
65
+ `${col.dataType}${pkNote}, ${nullable}.` +
66
+ `${fkNote}${commentNote}${defaultNote} ` +
67
+ `Part of table ${table.schema}.${table.name}.`).trim();
68
+ }
69
+ // ---------------------------------------------------------------------------
70
+ // indexSchema
71
+ // ---------------------------------------------------------------------------
72
+ /**
73
+ * Embed an IntrospectedSchema and upsert the results into schema_embeddings.
74
+ *
75
+ * Generates one chunk per table and one chunk per column, embeds them all
76
+ * in batches via the provider, then bulk-upserts into the DB.
77
+ */
78
+ export async function indexSchema(pool, schema, provider, _options = {}) {
79
+ if (schema.tables.length === 0) {
80
+ return { rowsUpserted: 0, totalTokens: 0 };
81
+ }
82
+ // Build all (objectType, objectRef, content) tuples
83
+ const chunks = [];
84
+ for (const table of schema.tables) {
85
+ chunks.push({
86
+ objectType: "table",
87
+ objectRef: `${table.schema}.${table.name}`,
88
+ content: describeTable(table),
89
+ });
90
+ for (let i = 0; i < table.columns.length; i++) {
91
+ const col = table.columns[i];
92
+ chunks.push({
93
+ objectType: "column",
94
+ objectRef: `${table.schema}.${table.name}.${col.name}`,
95
+ content: describeColumn(table, i),
96
+ });
97
+ }
98
+ }
99
+ // Embed all content strings (provider handles its own batching)
100
+ const texts = chunks.map((c) => c.content);
101
+ const embeddingResult = await provider.embed(texts, "document");
102
+ // Upsert into schema_embeddings
103
+ const client = await pool.connect();
104
+ let rowsUpserted = 0;
105
+ try {
106
+ await client.query("BEGIN");
107
+ for (let i = 0; i < chunks.length; i++) {
108
+ const chunk = chunks[i];
109
+ const vector = embeddingResult.embeddings[i];
110
+ const vectorLiteral = `[${vector.join(",")}]`;
111
+ await client.query(`INSERT INTO schema_embeddings (object_type, object_ref, content, embedding)
112
+ VALUES ($1, $2, $3, $4::vector)
113
+ ON CONFLICT (object_type, object_ref)
114
+ DO UPDATE SET
115
+ content = EXCLUDED.content,
116
+ embedding = EXCLUDED.embedding`, [chunk.objectType, chunk.objectRef, chunk.content, vectorLiteral]);
117
+ rowsUpserted++;
118
+ }
119
+ await client.query("COMMIT");
120
+ }
121
+ catch (err) {
122
+ await client.query("ROLLBACK").catch(() => undefined);
123
+ throw err;
124
+ }
125
+ finally {
126
+ client.release();
127
+ }
128
+ return { rowsUpserted, totalTokens: embeddingResult.totalTokens };
129
+ }
130
+ /**
131
+ * Embed a natural-language question and return the most relevant schema
132
+ * chunks from schema_embeddings, ranked by cosine similarity.
133
+ */
134
+ export async function retrieve(pool, question, provider, options = {}) {
135
+ const { topK = 10, minScore = 0, objectTypes } = options;
136
+ const embeddingResult = await provider.embed([question], "query");
137
+ const vector = embeddingResult.embeddings[0];
138
+ const vectorLiteral = `[${vector.join(",")}]`;
139
+ const typeFilter = objectTypes && objectTypes.length > 0
140
+ ? `AND object_type = ANY($3::text[])`
141
+ : "";
142
+ const params = [vectorLiteral, topK];
143
+ if (objectTypes && objectTypes.length > 0)
144
+ params.push(objectTypes);
145
+ const result = await pool.query(`SELECT
146
+ id,
147
+ object_type,
148
+ object_ref,
149
+ content,
150
+ 1 - (embedding <=> $1::vector) AS score
151
+ FROM schema_embeddings
152
+ WHERE 1 - (embedding <=> $1::vector) >= ${minScore}
153
+ ${typeFilter}
154
+ ORDER BY embedding <=> $1::vector
155
+ LIMIT $2`, params);
156
+ return result.rows.map((row) => ({
157
+ id: row.id,
158
+ objectType: row.object_type,
159
+ objectRef: row.object_ref,
160
+ content: row.content,
161
+ score: Number(row.score),
162
+ }));
163
+ }
164
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/retriever/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Validator
3
+ *
4
+ * AST-level safety check on generated SQL: rejects writes, DDL, multi-statement
5
+ * payloads, and known-dangerous functions (e.g. pg_sleep). Read-only enforcement
6
+ * lives here as the first line of defense (the read-only DB role is the second).
7
+ */
8
+ export interface ValidationResult {
9
+ valid: boolean;
10
+ /** Human-readable reason when valid === false. */
11
+ reason?: string;
12
+ }
13
+ /**
14
+ * Validate a SQL string for safety. Returns `{ valid: true }` for read-only
15
+ * SELECT / VALUES / WITH statements that contain no writes, DDL, or dangerous
16
+ * function calls; returns `{ valid: false, reason }` otherwise.
17
+ *
18
+ * Checks performed (in order):
19
+ * 1. Parse — reject if the SQL cannot be parsed at all.
20
+ * 2. Single statement — reject multi-statement payloads (`;` separated).
21
+ * 3. Allowed top-level type — must be `select`, `with`, or `values`.
22
+ * 4. No write operations inside CTEs (WITH clauses).
23
+ * 5. No dangerous function calls anywhere in the statement tree.
24
+ */
25
+ export declare function validate(sql: string): ValidationResult;
26
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/validator/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,kDAAkD;IAClD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAqID;;;;;;;;;;;GAWG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAoEtD"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * Validator
3
+ *
4
+ * AST-level safety check on generated SQL: rejects writes, DDL, multi-statement
5
+ * payloads, and known-dangerous functions (e.g. pg_sleep). Read-only enforcement
6
+ * lives here as the first line of defense (the read-only DB role is the second).
7
+ */
8
+ import { parse, astVisitor } from "pgsql-ast-parser";
9
+ // ---------------------------------------------------------------------------
10
+ // Constants
11
+ // ---------------------------------------------------------------------------
12
+ /**
13
+ * Statement types that are unconditionally forbidden.
14
+ * The planner should only ever emit a single SELECT, WITH, or VALUES.
15
+ */
16
+ const BANNED_STATEMENT_TYPES = new Set([
17
+ "insert",
18
+ "update",
19
+ "delete",
20
+ "drop table",
21
+ "drop",
22
+ "truncate table",
23
+ "create table",
24
+ "create index",
25
+ "create extension",
26
+ "create view",
27
+ "create materialized view",
28
+ "refresh materialized view",
29
+ "create sequence",
30
+ "create schema",
31
+ "create enum",
32
+ "create composite type",
33
+ "create function",
34
+ "drop function",
35
+ "alter table",
36
+ "alter index",
37
+ "alter sequence",
38
+ "alter enum",
39
+ "begin",
40
+ "commit",
41
+ "rollback",
42
+ "start transaction",
43
+ "set global",
44
+ "set timezone",
45
+ "set names",
46
+ "tablespace",
47
+ "do",
48
+ "raise",
49
+ "prepare",
50
+ "deallocate",
51
+ "comment",
52
+ ]);
53
+ /**
54
+ * PostgreSQL built-in functions that pose security or availability risks.
55
+ * These are rejected even inside an otherwise valid SELECT.
56
+ */
57
+ const DANGEROUS_FUNCTIONS = new Set([
58
+ // Process / availability
59
+ "pg_sleep",
60
+ "pg_terminate_backend",
61
+ "pg_cancel_backend",
62
+ "pg_reload_conf",
63
+ "pg_rotate_logfile",
64
+ "pg_switch_wal",
65
+ // File system access
66
+ "pg_read_file",
67
+ "pg_read_binary_file",
68
+ "pg_ls_dir",
69
+ "pg_ls_logdir",
70
+ "pg_ls_waldir",
71
+ "pg_ls_archive_statusdir",
72
+ "pg_ls_tmpdir",
73
+ "pg_stat_file",
74
+ "pg_file_write",
75
+ "pg_file_rename",
76
+ "pg_file_unlink",
77
+ // Network / foreign server
78
+ "dblink",
79
+ "dblink_connect",
80
+ "dblink_exec",
81
+ "dblink_open",
82
+ // Large object (file-like I/O)
83
+ "lo_import",
84
+ "lo_export",
85
+ "lo_create",
86
+ "lo_unlink",
87
+ // Copy (shell-out equivalent)
88
+ "pg_copy_logical_replication_slot",
89
+ ]);
90
+ // ---------------------------------------------------------------------------
91
+ // Internal helpers
92
+ // ---------------------------------------------------------------------------
93
+ /**
94
+ * Recursively collect all CTE binding statement types from a WITH statement.
95
+ * `with recursive` uses a different structure (its `bind` is a SelectFromUnion,
96
+ * not an array), so only `with` needs the recursive scan.
97
+ */
98
+ function collectWithBindingTypes(stmt) {
99
+ const types = [];
100
+ for (const binding of stmt.bind) {
101
+ const inner = binding.statement;
102
+ types.push(inner.type);
103
+ // Recurse into nested WITH
104
+ if (inner.type === "with") {
105
+ types.push(...collectWithBindingTypes(inner));
106
+ }
107
+ }
108
+ return types;
109
+ }
110
+ /**
111
+ * Walk the full AST of a statement and collect all function call names.
112
+ * Uses astVisitor for a deep, recursive traversal.
113
+ */
114
+ function collectFunctionCalls(stmt) {
115
+ const names = [];
116
+ const visitor = astVisitor(() => ({
117
+ call: (call) => {
118
+ const name = call.function?.name;
119
+ if (name)
120
+ names.push(name.toLowerCase());
121
+ visitor.super().call(call);
122
+ },
123
+ }));
124
+ visitor.statement(stmt);
125
+ return names;
126
+ }
127
+ // ---------------------------------------------------------------------------
128
+ // Public API
129
+ // ---------------------------------------------------------------------------
130
+ /**
131
+ * Validate a SQL string for safety. Returns `{ valid: true }` for read-only
132
+ * SELECT / VALUES / WITH statements that contain no writes, DDL, or dangerous
133
+ * function calls; returns `{ valid: false, reason }` otherwise.
134
+ *
135
+ * Checks performed (in order):
136
+ * 1. Parse — reject if the SQL cannot be parsed at all.
137
+ * 2. Single statement — reject multi-statement payloads (`;` separated).
138
+ * 3. Allowed top-level type — must be `select`, `with`, or `values`.
139
+ * 4. No write operations inside CTEs (WITH clauses).
140
+ * 5. No dangerous function calls anywhere in the statement tree.
141
+ */
142
+ export function validate(sql) {
143
+ // --- 1. Parse ----------------------------------------------------------
144
+ let statements;
145
+ try {
146
+ statements = parse(sql);
147
+ }
148
+ catch (err) {
149
+ return {
150
+ valid: false,
151
+ reason: `SQL parse error: ${err instanceof Error ? err.message : String(err)}`,
152
+ };
153
+ }
154
+ // --- 2. Single statement -----------------------------------------------
155
+ if (statements.length === 0) {
156
+ return { valid: false, reason: "Empty SQL statement: no statements parsed." };
157
+ }
158
+ if (statements.length > 1) {
159
+ return {
160
+ valid: false,
161
+ reason: `Multi-statement SQL is not allowed (${statements.length} statements found).`,
162
+ };
163
+ }
164
+ const stmt = statements[0];
165
+ // --- 3. Allowed top-level statement type --------------------------------
166
+ if (BANNED_STATEMENT_TYPES.has(stmt.type)) {
167
+ return {
168
+ valid: false,
169
+ reason: `Statement type '${stmt.type}' is not allowed. Only SELECT queries are permitted.`,
170
+ };
171
+ }
172
+ const ALLOWED_TOP_LEVEL = new Set(["select", "with", "with recursive", "values", "union"]);
173
+ if (!ALLOWED_TOP_LEVEL.has(stmt.type)) {
174
+ return {
175
+ valid: false,
176
+ reason: `Statement type '${stmt.type}' is not allowed. Only SELECT queries are permitted.`,
177
+ };
178
+ }
179
+ // --- 4. No write operations in CTEs ------------------------------------
180
+ // Only `with` (not `with recursive`) has array-of-bindings; `with recursive`
181
+ // binds a SelectFromUnion which cannot contain write statements.
182
+ if (stmt.type === "with") {
183
+ const innerTypes = collectWithBindingTypes(stmt);
184
+ for (const innerType of innerTypes) {
185
+ if (BANNED_STATEMENT_TYPES.has(innerType)) {
186
+ return {
187
+ valid: false,
188
+ reason: `Write operation '${innerType}' is not allowed inside a CTE.`,
189
+ };
190
+ }
191
+ }
192
+ }
193
+ // --- 5. No dangerous function calls ------------------------------------
194
+ const calls = collectFunctionCalls(stmt);
195
+ for (const name of calls) {
196
+ if (DANGEROUS_FUNCTIONS.has(name)) {
197
+ return {
198
+ valid: false,
199
+ reason: `Function '${name}' is not allowed.`,
200
+ };
201
+ }
202
+ }
203
+ return { valid: true };
204
+ }
205
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/validator/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@pgsage/core",
3
+ "version": "0.1.0",
4
+ "description": "Schema-aware SQL agent SDK: introspection, retrieval, planning, validation, and safe execution.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/hownowbrowncow/pgsage.git",
21
+ "directory": "packages/core"
22
+ },
23
+ "homepage": "https://github.com/hownowbrowncow/pgsage/tree/main/packages/core",
24
+ "bugs": "https://github.com/hownowbrowncow/pgsage/issues",
25
+ "keywords": [
26
+ "postgres",
27
+ "postgresql",
28
+ "sql",
29
+ "ai",
30
+ "agent",
31
+ "pgvector",
32
+ "claude",
33
+ "anthropic",
34
+ "natural-language",
35
+ "embeddings",
36
+ "text-to-sql",
37
+ "llm"
38
+ ],
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "peerDependencies": {
43
+ "pg": ">=8.0.0"
44
+ },
45
+ "devDependencies": {
46
+ "@types/node": "26.2.0",
47
+ "@types/pg": "8.23.1",
48
+ "pg": "8.23.0",
49
+ "typescript": "6.0.3",
50
+ "vitest": "4.1.11"
51
+ },
52
+ "dependencies": {
53
+ "@mastra/core": "^1.61.0",
54
+ "pgsql-ast-parser": "^12.0.2",
55
+ "zod": "^4.4.3"
56
+ },
57
+ "scripts": {
58
+ "build": "tsc -p tsconfig.json",
59
+ "typecheck": "tsc -p tsconfig.typecheck.json",
60
+ "lint": "eslint .",
61
+ "test": "vitest run"
62
+ }
63
+ }