agent-sql 0.3.3 → 0.3.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -28
- package/dist/index.mjs +4 -1
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -19,6 +19,8 @@ agent-sql works by fully parsing the supplied SQL query into an AST and transfor
|
|
|
19
19
|
- **`JOIN`s added:** if needed to reach the guard tenant tables (save on tokens).
|
|
20
20
|
- **No sneaky joins:** no `join secrets on true`. We have your back.
|
|
21
21
|
|
|
22
|
+
I plan to support inserts, updates, CTEs, subqueries once I can convincingly make them safe.
|
|
23
|
+
|
|
22
24
|
## Quickstart
|
|
23
25
|
|
|
24
26
|
```bash
|
|
@@ -28,7 +30,7 @@ npm install agent-sql
|
|
|
28
30
|
```ts
|
|
29
31
|
import { agentSql } from "agent-sql";
|
|
30
32
|
|
|
31
|
-
const sql = agentSql(
|
|
33
|
+
const sql = agentSql("SELECT * FROM msg", "msg.tenant_id", 123);
|
|
32
34
|
|
|
33
35
|
console.log(sql);
|
|
34
36
|
// SELECT *
|
|
@@ -39,30 +41,81 @@ console.log(sql);
|
|
|
39
41
|
|
|
40
42
|
## Usage
|
|
41
43
|
|
|
42
|
-
### Define
|
|
44
|
+
### Define a schema
|
|
43
45
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
46
|
+
In the simple example above, all `JOIN`s will be blocked.
|
|
47
|
+
For agent-sql to know what joins and tables permit, you need to define a schema.
|
|
48
|
+
Heads up: if you use Drizzle, you can just [use your Drizzle schema](#integration-with-ai-sdk-and-drizzle).
|
|
47
49
|
|
|
48
50
|
```ts
|
|
49
51
|
import { createAgentSql, defineSchema } from "agent-sql";
|
|
50
|
-
import { tool } from "ai";
|
|
51
|
-
import { sql } from "drizzle-orm";
|
|
52
|
-
import { db } from "@/db";
|
|
53
52
|
|
|
54
53
|
// Define your schema.
|
|
55
54
|
// Only the tables listed will be permitted
|
|
56
55
|
// Joins can only use the FKs defined here
|
|
57
56
|
const schema = defineSchema({
|
|
58
|
-
|
|
59
|
-
msg: {
|
|
57
|
+
tenant: { id: null },
|
|
58
|
+
msg: { tenant_id: { ft: "tenant", fc: "id" } },
|
|
60
59
|
});
|
|
61
60
|
|
|
62
|
-
|
|
61
|
+
// Use your schema from above
|
|
62
|
+
// Specify 1+ column->value pairs that will be enforced
|
|
63
|
+
const agentSql = createAgentSql(schema, { "tenant.id": 123 });
|
|
64
|
+
|
|
65
|
+
// Now use it
|
|
66
|
+
const sql = agentSql("SELECT * FROM msg");
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Outputs:
|
|
70
|
+
|
|
71
|
+
```sql
|
|
72
|
+
SELECT
|
|
73
|
+
msg.* -- qualify the *
|
|
74
|
+
FROM msg
|
|
75
|
+
INNER JOIN tenant -- add the needed join for the guard
|
|
76
|
+
ON tenant.id = msg.tenant_id -- use the schema to join correctly
|
|
77
|
+
WHERE tenant.id = 123 -- apply the guard
|
|
78
|
+
LIMIT 10000 -- limit the rows
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Bad stuff is blocked
|
|
82
|
+
|
|
83
|
+
The following query will be blocked (many times over).
|
|
84
|
+
|
|
85
|
+
```sql
|
|
86
|
+
SELECT
|
|
87
|
+
sneaky_func('./bad_file') -- won't pass whitelist
|
|
88
|
+
FROM secret
|
|
89
|
+
JOIN random -- not an approved table
|
|
90
|
+
ON random.id = secret.id -- not an approved FK pair
|
|
91
|
+
JOIN danger -- disconnected from join graph
|
|
92
|
+
ON true -- not allowed
|
|
93
|
+
WHERE true -- won't trick anyone
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Integration with AI SDK and Drizzle
|
|
97
|
+
|
|
98
|
+
If you're using Drizzle, you can skip the schema step and use the one you already have!
|
|
99
|
+
|
|
100
|
+
Just pass it through, and `agentSql` will respect your schema.
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
import { tool } from "ai";
|
|
104
|
+
import { sql } from "drizzle-orm";
|
|
105
|
+
|
|
106
|
+
import { createAgentSql } from "agent-sql";
|
|
107
|
+
import { defineSchemaFromDrizzle } from "agent-sql/drizzle";
|
|
108
|
+
|
|
109
|
+
import { db } from "@/db";
|
|
110
|
+
import * as drizzleSchema from "@/db/schema";
|
|
111
|
+
|
|
112
|
+
// No need to re-enter your schema, we'll pull it in from Drizzle
|
|
113
|
+
const schema = defineSchemaFromDrizzle(drizzleSchema);
|
|
114
|
+
|
|
115
|
+
function makeSqlTool(tenantId: string) {
|
|
63
116
|
// Create a sanitiser function for this tenant
|
|
64
117
|
// Specify one or more column->value pairs that will be enforced
|
|
65
|
-
const agentSql = createAgentSql(schema, { "
|
|
118
|
+
const agentSql = createAgentSql(schema, { "tenant.id": tenantId });
|
|
66
119
|
|
|
67
120
|
return tool({
|
|
68
121
|
description: "Run raw SQL against the DB",
|
|
@@ -70,34 +123,22 @@ function makeSqlTool(userId: string) {
|
|
|
70
123
|
execute: async ({ query }) => {
|
|
71
124
|
// The LLM can pass any query it likes, we'll sanitise it if possible
|
|
72
125
|
// and return helpful error messages if not
|
|
73
|
-
const
|
|
126
|
+
const sql = agentSql(query);
|
|
74
127
|
// Now we can throw that straight at the db and be confident it'll only
|
|
75
128
|
// return data from the specified tenant
|
|
76
|
-
return db.execute(sql.raw(
|
|
129
|
+
return db.execute(sql.raw(sql));
|
|
77
130
|
},
|
|
78
131
|
});
|
|
79
132
|
}
|
|
80
133
|
```
|
|
81
134
|
|
|
82
|
-
###
|
|
83
|
-
|
|
84
|
-
If you're using Drizzle, you can skip the schema step and use the one you already have!
|
|
135
|
+
### If you don't want your whole Drizzle schema available
|
|
85
136
|
|
|
86
|
-
|
|
137
|
+
You can also exclude tables if you don't want agents to see them:
|
|
87
138
|
|
|
88
139
|
```ts
|
|
89
140
|
import { defineSchemaFromDrizzle } from "agent-sql/drizzle";
|
|
90
|
-
import * as drizzleSchema from "@/db/schema";
|
|
91
|
-
|
|
92
|
-
const schema = defineSchemaFromDrizzle(drizzleSchema);
|
|
93
141
|
|
|
94
|
-
// The rest as before...
|
|
95
|
-
const agentSql = createAgentSql(schema, { "user.id": userId });
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
You can also exclude tables if you don't want agents to see them:
|
|
99
|
-
|
|
100
|
-
```ts
|
|
101
142
|
const schema = defineSchemaFromDrizzle(drizzleSchema, {
|
|
102
143
|
exclude: ["api_keys"],
|
|
103
144
|
});
|
package/dist/index.mjs
CHANGED
|
@@ -7451,7 +7451,10 @@ semantics.addOperation("toAST()", {
|
|
|
7451
7451
|
});
|
|
7452
7452
|
function parseSql(expr) {
|
|
7453
7453
|
const matchResult = result.match(expr);
|
|
7454
|
-
if (matchResult.failed())
|
|
7454
|
+
if (matchResult.failed()) {
|
|
7455
|
+
const [message] = matchResult.message.split("\nExpected");
|
|
7456
|
+
return Err(new ParseError(`Got invalid SQL. Some language features are deliberately disabled. Re-write your query without them.\n${message}`));
|
|
7457
|
+
}
|
|
7455
7458
|
try {
|
|
7456
7459
|
return Ok(semantics(matchResult).toAST());
|
|
7457
7460
|
} catch (e) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-sql",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "A starter for creating a TypeScript package.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agent",
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"build": "vp pack",
|
|
35
35
|
"dev": "vp pack --watch",
|
|
36
36
|
"test": "vp test",
|
|
37
|
+
"coverage": "vp test --coverage",
|
|
37
38
|
"check": "vp check",
|
|
38
39
|
"prepublishOnly": "vp run build",
|
|
39
40
|
"ohm": "ohm generateBundles --withTypes --esm 'src/sql.ohm'",
|
|
@@ -48,6 +49,7 @@
|
|
|
48
49
|
"@types/node": "^25.5.0",
|
|
49
50
|
"@types/pg": "^8.18.0",
|
|
50
51
|
"@typescript/native-preview": "7.0.0-dev.20260316.1",
|
|
52
|
+
"@vitest/coverage-v8": "^4.1.0",
|
|
51
53
|
"bumpp": "^11.0.1",
|
|
52
54
|
"drizzle-orm": "^0.45.1",
|
|
53
55
|
"pg": "^8.20.0",
|