agent-sql 0.2.0 → 0.2.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Chris Arderne
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -55,17 +55,14 @@ import { db } from "@/db";
55
55
  // Only the tables listed will be permitted
56
56
  // Joins can only use the FKs defined here
57
57
  const schema = defineSchema({
58
- user: { id },
59
- msg: { userId: { user: "id" } },
58
+ user: { id: null },
59
+ msg: { userId: { ft: "user", fc: "id" } },
60
60
  });
61
61
 
62
62
  function makeSqlTool(userId: string) {
63
63
  // Create a sanitiser function for this tenant
64
- const agentSql = createAgentSql({
65
- column: "user.id",
66
- value: userId,
67
- schema,
68
- });
64
+ // Specify one or more column->value pairs that will be enforced
65
+ const agentSql = createAgentSql(schema, { "user.id": userId });
69
66
 
70
67
  return tool({
71
68
  description: "Run raw SQL against the DB",
@@ -95,11 +92,7 @@ import * as drizzleSchema from "@/db/schema";
95
92
  const schema = defineSchemaFromDrizzle(drizzleSchema);
96
93
 
97
94
  // The rest as before...
98
- const agentSql = createAgentSql({
99
- column: "user.id",
100
- value: userId,
101
- schema,
102
- });
95
+ const agentSql = createAgentSql(schema, { "user.id": userId });
103
96
  ```
104
97
 
105
98
  You can also exclude tables if you don't want agents to see them:
package/dist/index.d.mts CHANGED
@@ -10,10 +10,9 @@ interface GuardCol {
10
10
  type WhereGuard = GuardCol & {
11
11
  value: GuardVal;
12
12
  };
13
- declare function addGuards(ast: SelectStatement, guard: WhereGuard, limit?: number): Result<SelectStatement>;
14
- //#endregion
15
- //#region src/namespec.d.ts
16
13
  type OneOrTwoDots<S extends string> = S extends `${infer A}.${infer B}.${infer C}` ? A extends `${string}.${string}` ? never : B extends `${string}.${string}` ? never : C extends `${string}.${string}` ? never : S : S extends `${infer A}.${infer B}` ? A extends `${string}.${string}` ? never : B extends `${string}.${string}` ? never : S : never;
14
+ type SchemaGuardKeys<T> = { [Table in keyof T & string]: `${Table}.${keyof T[Table] & string}` }[keyof T & string];
15
+ declare function applyGuards(ast: SelectStatement, guards: WhereGuard[], limit?: number): Result<SelectStatement>;
17
16
  //#endregion
18
17
  //#region src/output.d.ts
19
18
  declare function outputSql(ast: SelectStatement): string;
@@ -29,33 +28,13 @@ declare function agentSql<S extends string>(sql: string, column: S & OneOrTwoDot
29
28
  schema?: Schema;
30
29
  limit?: number;
31
30
  }): string;
32
- declare function createAgentSql<S extends string>(_: {
33
- column: S & OneOrTwoDots<S>;
34
- value: GuardVal;
35
- schema?: Schema;
31
+ declare function createAgentSql<T extends Schema, S extends SchemaGuardKeys<T>>(schema: T, guards: Record<S, GuardVal>, opts: {
36
32
  limit?: number;
37
33
  throws: false;
38
34
  }): (expr: string) => Result<string>;
39
- declare function createAgentSql<S extends string>(_: {
40
- column: S & OneOrTwoDots<S>;
41
- value: GuardVal;
42
- schema?: Schema;
35
+ declare function createAgentSql<T extends Schema, S extends SchemaGuardKeys<T>>(schema: T, guards: Record<S, GuardVal>, opts?: {
43
36
  limit?: number;
44
37
  throws?: true;
45
38
  }): (expr: string) => string;
46
- declare function createAgentSql<S extends string>(_: {
47
- column: S & OneOrTwoDots<S>;
48
- value?: undefined;
49
- schema?: Schema;
50
- limit?: number;
51
- throws: false;
52
- }): (guardVal: GuardVal) => (expr: string) => Result<string>;
53
- declare function createAgentSql<S extends string>(_: {
54
- column: S & OneOrTwoDots<S>;
55
- value?: undefined;
56
- schema?: Schema;
57
- limit?: number;
58
- throws?: true;
59
- }): (guardVal: GuardVal) => (expr: string) => string;
60
39
  //#endregion
61
- export { agentSql, createAgentSql, defineSchema, outputSql, parseSql, addGuards as sanitiseSql };
40
+ export { agentSql, createAgentSql, defineSchema, outputSql, parseSql, applyGuards as sanitiseSql };
package/dist/index.mjs CHANGED
@@ -247,54 +247,86 @@ function returnOrThrow(result, throws) {
247
247
  //#endregion
248
248
  //#region src/guard.ts
249
249
  const DEFAULT_LIMIT = 1e4;
250
- function addGuards(ast, guard, limit = DEFAULT_LIMIT) {
251
- const ast2 = addWhereGuard(ast, guard);
250
+ function applyGuards(ast, guards, limit = DEFAULT_LIMIT) {
251
+ const ast2 = addWhereGuard(ast, guards);
252
252
  if (!ast2.ok) return ast2;
253
253
  return addLimitGuard(ast2.data, limit);
254
254
  }
255
- function addWhereGuard(ast, guard) {
256
- const { schema, table, column, value } = guard;
257
- const tableRef = {
258
- type: "table_ref",
259
- schema,
260
- name: table
261
- };
262
- if (!checkIfTableRefExists(ast, tableRef)) return Err(new SanitiseError(`The table '${handleTableRef(tableRef)}' must appear in the FROM or JOIN clauses.`));
263
- const newClause = {
264
- type: "where_comparison",
265
- operator: "=",
266
- left: {
267
- type: "where_value",
268
- kind: "column_ref",
269
- ref: {
270
- type: "column_ref",
271
- schema,
272
- table,
273
- name: column
274
- }
275
- },
276
- right: typeof value === "string" ? {
277
- type: "where_value",
278
- kind: "string",
279
- value
280
- } : {
281
- type: "where_value",
282
- kind: "integer",
255
+ function resolveGuards(guards) {
256
+ if (guards.length === 0) return Err(new AgentSqlError("At least one guard must be provided."));
257
+ const result = [];
258
+ for (const [column, value] of Object.entries(guards)) {
259
+ const guardCol = resolveSingleGuardCol(column);
260
+ if (!guardCol.ok) return guardCol;
261
+ result.push({
262
+ ...guardCol.data,
283
263
  value
284
- }
285
- };
264
+ });
265
+ }
266
+ return Ok(result);
267
+ }
268
+ function resolveSingleGuardCol(column) {
269
+ const [a, b, c] = column.split(".");
270
+ if (a === void 0 || b === void 0) return Err(new AgentSqlError(`Malformed column string: '${column}'. Pass 'table.column'.`));
271
+ if (c === void 0) return Ok({
272
+ table: a,
273
+ column: b
274
+ });
275
+ return Err(new AgentSqlError("Specifying guard as schema.table.name not yet supported"));
276
+ }
277
+ function addWhereGuard(ast, guards) {
278
+ for (const guard of guards) {
279
+ const tableRef = {
280
+ type: "table_ref",
281
+ schema: guard.schema,
282
+ name: guard.table
283
+ };
284
+ if (!checkIfTableRefExists(ast, tableRef)) return Err(new SanitiseError(`The table '${handleTableRef(tableRef)}' must appear in the FROM or JOIN clauses.`));
285
+ }
286
+ const [first, ...rest] = guards.map((guard) => {
287
+ const { schema, table, column, value } = guard;
288
+ return {
289
+ type: "where_comparison",
290
+ operator: "=",
291
+ left: {
292
+ type: "where_value",
293
+ kind: "column_ref",
294
+ ref: {
295
+ type: "column_ref",
296
+ schema,
297
+ table,
298
+ name: column
299
+ }
300
+ },
301
+ right: typeof value === "string" ? {
302
+ type: "where_value",
303
+ kind: "string",
304
+ value
305
+ } : {
306
+ type: "where_value",
307
+ kind: "integer",
308
+ value
309
+ }
310
+ };
311
+ });
312
+ if (!first) return Err(new AgentSqlError("No guards were provided"));
313
+ const combined = rest.reduce((acc, clause) => ({
314
+ type: "where_and",
315
+ left: acc,
316
+ right: clause
317
+ }), first);
286
318
  return Ok({
287
319
  ...ast,
288
320
  where: ast.where ? {
289
321
  type: "where_root",
290
322
  inner: {
291
323
  type: "where_and",
292
- left: newClause,
324
+ left: combined,
293
325
  right: ast.where.inner
294
326
  }
295
327
  } : {
296
328
  type: "where_root",
297
- inner: newClause
329
+ inner: combined
298
330
  }
299
331
  });
300
332
  }
@@ -358,21 +390,6 @@ function getJoinTableRef(joinTableName, left, right) {
358
390
  };
359
391
  }
360
392
  //#endregion
361
- //#region src/namespec.ts
362
- function getQualifiedColumnFromString(column) {
363
- const [a, b, c] = column.split(".");
364
- if (a === void 0 || b === void 0) throw new AgentSqlError(`Malformed column string: '${column}'. Pass 'table.column'.`);
365
- if (c === void 0) return Ok({
366
- table: a,
367
- column: b
368
- });
369
- return Ok({
370
- schema: a,
371
- table: b,
372
- column: c
373
- });
374
- }
375
- //#endregion
376
393
  //#region src/sql.ohm-bundle.js
377
394
  const result = makeRecipe([
378
395
  "grammar",
@@ -6433,60 +6450,37 @@ function parseSql(expr) {
6433
6450
  //#region src/index.ts
6434
6451
  function agentSql(sql, column, value, { schema, limit } = {}) {
6435
6452
  return privateAgentSql(sql, {
6436
- column,
6437
- value,
6453
+ guards: { [column]: value },
6438
6454
  schema,
6439
6455
  limit,
6440
6456
  throws: true
6441
6457
  });
6442
6458
  }
6443
- function createAgentSql({ column, schema, value, limit, throws = true }) {
6444
- if (value !== void 0) return (expr) => throws ? privateAgentSql(expr, {
6445
- column,
6446
- value,
6459
+ function createAgentSql(schema, guards, { limit, throws = true } = {}) {
6460
+ return (expr) => throws ? privateAgentSql(expr, {
6461
+ guards,
6447
6462
  schema,
6448
6463
  limit,
6449
6464
  throws
6450
6465
  }) : privateAgentSql(expr, {
6451
- column,
6452
- value,
6466
+ guards,
6453
6467
  schema,
6454
6468
  limit,
6455
6469
  throws
6456
6470
  });
6457
- function factory(guardVal) {
6458
- return throws ? createAgentSql({
6459
- column,
6460
- schema,
6461
- value: guardVal,
6462
- limit,
6463
- throws
6464
- }) : createAgentSql({
6465
- column,
6466
- schema,
6467
- value: guardVal,
6468
- limit,
6469
- throws
6470
- });
6471
- }
6472
- return factory;
6473
6471
  }
6474
- function privateAgentSql(sql, { column, value, schema, limit, throws }) {
6475
- const guardCol = getQualifiedColumnFromString(column);
6476
- if (!guardCol.ok) throw guardCol.error;
6472
+ function privateAgentSql(sql, { guards: guardsRaw, schema, limit, throws }) {
6473
+ const guards = resolveGuards(guardsRaw);
6474
+ if (!guards.ok) throw guards.error;
6477
6475
  const ast = parseSql(sql);
6478
6476
  if (!ast.ok) return returnOrThrow(ast, throws);
6479
6477
  const ast2 = checkJoins(ast.data, schema);
6480
6478
  if (!ast2.ok) return returnOrThrow(ast2, throws);
6481
- const where = {
6482
- ...guardCol.data,
6483
- value
6484
- };
6485
- const san = addGuards(ast2.data, where, limit);
6479
+ const san = applyGuards(ast2.data, guards.data, limit);
6486
6480
  if (!san.ok) return returnOrThrow(san, throws);
6487
6481
  const res = outputSql(san.data);
6488
6482
  if (throws) return res;
6489
6483
  return Ok(res);
6490
6484
  }
6491
6485
  //#endregion
6492
- export { agentSql, createAgentSql, defineSchema, outputSql, parseSql, addGuards as sanitiseSql };
6486
+ export { agentSql, createAgentSql, defineSchema, outputSql, parseSql, applyGuards as sanitiseSql };
package/package.json CHANGED
@@ -1,7 +1,13 @@
1
1
  {
2
2
  "name": "agent-sql",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "A starter for creating a TypeScript package.",
5
+ "keywords": [
6
+ "agent",
7
+ "ai",
8
+ "postgres",
9
+ "sql"
10
+ ],
5
11
  "homepage": "https://github.com/carderne/agent-sql#readme",
6
12
  "bugs": {
7
13
  "url": "https://github.com/carderne/agent-sql/issues"