@reasoningco/infer 0.0.2

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 (59) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +42 -0
  3. package/dist/catalog.d.ts +39 -0
  4. package/dist/catalog.d.ts.map +1 -0
  5. package/dist/catalog.js +249 -0
  6. package/dist/catalog.js.map +1 -0
  7. package/dist/connectors.d.ts +63 -0
  8. package/dist/connectors.d.ts.map +1 -0
  9. package/dist/connectors.js +263 -0
  10. package/dist/connectors.js.map +1 -0
  11. package/dist/dates.d.ts +35 -0
  12. package/dist/dates.d.ts.map +1 -0
  13. package/dist/dates.js +293 -0
  14. package/dist/dates.js.map +1 -0
  15. package/dist/entity-resolver.d.ts +29 -0
  16. package/dist/entity-resolver.d.ts.map +1 -0
  17. package/dist/entity-resolver.js +91 -0
  18. package/dist/entity-resolver.js.map +1 -0
  19. package/dist/errors.d.ts +10 -0
  20. package/dist/errors.d.ts.map +1 -0
  21. package/dist/errors.js +28 -0
  22. package/dist/errors.js.map +1 -0
  23. package/dist/index.d.ts +43 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +240 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/llm.d.ts +50 -0
  28. package/dist/llm.d.ts.map +1 -0
  29. package/dist/llm.js +311 -0
  30. package/dist/llm.js.map +1 -0
  31. package/dist/query-validator.d.ts +6 -0
  32. package/dist/query-validator.d.ts.map +1 -0
  33. package/dist/query-validator.js +136 -0
  34. package/dist/query-validator.js.map +1 -0
  35. package/dist/schemas.d.ts +170 -0
  36. package/dist/schemas.d.ts.map +1 -0
  37. package/dist/schemas.js +44 -0
  38. package/dist/schemas.js.map +1 -0
  39. package/dist/security.d.ts +9 -0
  40. package/dist/security.d.ts.map +1 -0
  41. package/dist/security.js +97 -0
  42. package/dist/security.js.map +1 -0
  43. package/dist/sql-builder.d.ts +5 -0
  44. package/dist/sql-builder.d.ts.map +1 -0
  45. package/dist/sql-builder.js +121 -0
  46. package/dist/sql-builder.js.map +1 -0
  47. package/dist/sql-validator.d.ts +8 -0
  48. package/dist/sql-validator.d.ts.map +1 -0
  49. package/dist/sql-validator.js +160 -0
  50. package/dist/sql-validator.js.map +1 -0
  51. package/dist/testing.d.ts +22 -0
  52. package/dist/testing.d.ts.map +1 -0
  53. package/dist/testing.js +78 -0
  54. package/dist/testing.js.map +1 -0
  55. package/dist/types.d.ts +214 -0
  56. package/dist/types.d.ts.map +1 -0
  57. package/dist/types.js +27 -0
  58. package/dist/types.js.map +1 -0
  59. package/package.json +72 -0
@@ -0,0 +1,44 @@
1
+ import { z } from "zod";
2
+ // ── Filter schema ──
3
+ export const FilterSchema = z.object({
4
+ field: z.string(),
5
+ op: z.enum(["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike"]),
6
+ value: z.union([
7
+ z.string(),
8
+ z.number(),
9
+ z.boolean(),
10
+ z.array(z.union([z.string(), z.number()])),
11
+ ]),
12
+ });
13
+ // ── Entity filter schema ──
14
+ export const EntityFilterSchema = z.object({
15
+ resolver: z.string(),
16
+ input: z.string(),
17
+ outputColumn: z.string(),
18
+ });
19
+ // ── Date range schema ──
20
+ export const DateRangeSchema = z.object({
21
+ mode: z.enum(["relative", "explicit", "latest", "none"]),
22
+ phrase: z.string().optional(),
23
+ startDate: z.string().optional(),
24
+ endDate: z.string().optional(),
25
+ });
26
+ // ── Order-by schema ──
27
+ export const OrderBySchema = z.object({
28
+ field: z.string(),
29
+ dir: z.enum(["asc", "desc"]).default("asc"),
30
+ });
31
+ // ── Smart query plan schema ──
32
+ export const SmartQueryPlanSchema = z.object({
33
+ dataset: z.string(),
34
+ select: z.array(z.string()).optional().default([]),
35
+ metrics: z.array(z.string()).optional().default([]),
36
+ groupBy: z.array(z.string()).optional().default([]),
37
+ filters: z.array(FilterSchema).optional().default([]),
38
+ entityFilters: z.array(EntityFilterSchema).optional().default([]),
39
+ dateRange: DateRangeSchema.optional(),
40
+ orderBy: z.array(OrderBySchema).optional().default([]),
41
+ limit: z.number().int().positive().max(200).optional(),
42
+ answerMode: z.enum(["rows", "aggregate_table", "scalar"]).optional().default("rows"),
43
+ });
44
+ //# sourceMappingURL=schemas.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare function validateInput(input: string): void;
2
+ export type ClassificationResult = {
3
+ safe: boolean;
4
+ confidence: number;
5
+ reason?: string;
6
+ };
7
+ export declare function classifyInjection(input: string): ClassificationResult;
8
+ export declare function sanitizeResult(rows: Record<string, unknown>[], sensitiveColumns: readonly string[]): Record<string, unknown>[];
9
+ //# sourceMappingURL=security.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAaA,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAkBjD;AAGD,MAAM,MAAM,oBAAoB,GAAG;IACjC,IAAI,EAAE,OAAO,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAuCF,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,oBAAoB,CAkBrE;AAGD,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAC/B,gBAAgB,EAAE,SAAS,MAAM,EAAE,GAClC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAY3B"}
@@ -0,0 +1,97 @@
1
+ import { InferError } from './errors.js';
2
+ // ── Input validation ──
3
+ const MAX_INPUT_LENGTH = 500;
4
+ // PII patterns
5
+ const PII_PATTERNS = [
6
+ /\b\d{3}-\d{2}-\d{4}\b/, // SSN
7
+ /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/, // Credit card
8
+ /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z]{2,}\b/i, // Email
9
+ /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, // Phone number
10
+ ];
11
+ export function validateInput(input) {
12
+ if (input.length > MAX_INPUT_LENGTH) {
13
+ throw new InferError('INPUT_TOO_LONG', 'input', `Input exceeds maximum length of ${MAX_INPUT_LENGTH} characters`);
14
+ }
15
+ if (input.trim().length === 0) {
16
+ throw new InferError('INPUT_TOO_LONG', 'input', 'Input cannot be empty');
17
+ }
18
+ // Check for PII
19
+ for (const pattern of PII_PATTERNS) {
20
+ if (pattern.test(input)) {
21
+ throw new InferError('INPUT_PII_DETECTED', 'input', 'Input appears to contain personal information');
22
+ }
23
+ }
24
+ // Check for prompt injection
25
+ const result = classifyInjection(input);
26
+ if (!result.safe) {
27
+ throw new InferError('INPUT_INJECTION_BLOCKED', 'input', 'Input blocked by security classifier');
28
+ }
29
+ }
30
+ const INJECTION_PATTERNS = [
31
+ { pattern: /ignore\s+(all\s+)?previous\s+instructions/i, reason: 'instruction override attempt' },
32
+ { pattern: /you\s+are\s+now/i, reason: 'role reassignment attempt' },
33
+ { pattern: /system\s*prompt/i, reason: 'system prompt probe' },
34
+ { pattern: /\bDROP\b/i, reason: 'SQL DROP keyword' },
35
+ { pattern: /\bDELETE\s+FROM\b/i, reason: 'SQL DELETE keyword' },
36
+ { pattern: /\bINSERT\s+INTO\b/i, reason: 'SQL INSERT keyword' },
37
+ { pattern: /\bUPDATE\b.*\bSET\b/i, reason: 'SQL UPDATE keyword' },
38
+ { pattern: /\bALTER\b/i, reason: 'SQL ALTER keyword' },
39
+ { pattern: /\bTRUNCATE\b/i, reason: 'SQL TRUNCATE keyword' },
40
+ { pattern: /\bGRANT\b/i, reason: 'SQL GRANT keyword' },
41
+ { pattern: /\bREVOKE\b/i, reason: 'SQL REVOKE keyword' },
42
+ { pattern: /\bpg_catalog\b/i, reason: 'system catalog reference' },
43
+ { pattern: /\binformation_schema\b/i, reason: 'information schema reference' },
44
+ { pattern: /\bpg_shadow\b/i, reason: 'pg_shadow reference' },
45
+ { pattern: /\bpg_stat\b/i, reason: 'pg_stat reference' },
46
+ { pattern: /\breturn\s+(json|sql|raw)\b/i, reason: 'output format manipulation' },
47
+ { pattern: /\bpg_sleep\b/i, reason: 'pg_sleep DoS attempt' },
48
+ { pattern: /\bpg_read_file\b/i, reason: 'file read attempt' },
49
+ { pattern: /\bdblink\b/i, reason: 'dblink access attempt' },
50
+ { pattern: /\blo_import\b|\blo_export\b/i, reason: 'large object manipulation' },
51
+ { pattern: /\bpg_terminate_backend\b/i, reason: 'backend termination attempt' },
52
+ { pattern: /\bCOPY\b.*\bTO\b/i, reason: 'COPY command' },
53
+ { pattern: /\bEXPLAIN\b/i, reason: 'EXPLAIN command' },
54
+ { pattern: /;\s*(DROP|DELETE|INSERT|UPDATE|ALTER)/i, reason: 'SQL injection via semicolon' },
55
+ { pattern: /'\s*(OR|AND)\s+'?\d*'?\s*=\s*'?\d*'?/i, reason: 'SQL injection via tautology' },
56
+ { pattern: /\bunion\s+select\b/i, reason: 'UNION injection' },
57
+ { pattern: /<!--.*-->/i, reason: 'HTML comment injection' },
58
+ { pattern: /\{\{.*\}\}/i, reason: 'template injection' },
59
+ { pattern: /\bact\s+as\b/i, reason: 'role play attempt' },
60
+ { pattern: /\bpretend\s+(to\s+be|you're|you\s+are)\b/i, reason: 'role play attempt' },
61
+ { pattern: /\bdo\s+not\s+follow\b/i, reason: 'instruction override attempt' },
62
+ { pattern: /\boverride\b.*\brules?\b/i, reason: 'rule override attempt' },
63
+ { pattern: /\bjailbreak\b/i, reason: 'jailbreak attempt' },
64
+ { pattern: /\bDAN\b.*\bmode\b/i, reason: 'DAN mode attempt' },
65
+ ];
66
+ export function classifyInjection(input) {
67
+ // Normalize via NFKC before checking
68
+ const normalized = input.normalize('NFKC');
69
+ for (const { pattern, reason } of INJECTION_PATTERNS) {
70
+ if (pattern.test(normalized)) {
71
+ return { safe: false, confidence: 0.95, reason };
72
+ }
73
+ }
74
+ // Structural analysis: high ratio of SQL tokens to natural language
75
+ const sqlTokens = (normalized.match(/\b(SELECT|FROM|WHERE|JOIN|GROUP|ORDER|HAVING|UNION|INSERT|DELETE|UPDATE|DROP|ALTER|CREATE|SET|LIMIT)\b/gi) || []).length;
76
+ const wordCount = normalized.split(/\s+/).length;
77
+ if (wordCount > 3 && sqlTokens / wordCount > 0.4) {
78
+ return { safe: false, confidence: 0.8, reason: 'high SQL token density' };
79
+ }
80
+ return { safe: true, confidence: 0.95 };
81
+ }
82
+ // ── Result sanitization ──
83
+ export function sanitizeResult(rows, sensitiveColumns) {
84
+ if (sensitiveColumns.length === 0)
85
+ return rows;
86
+ const sensitiveSet = new Set(sensitiveColumns);
87
+ return rows.map(row => {
88
+ const sanitized = {};
89
+ for (const [key, value] of Object.entries(row)) {
90
+ if (!sensitiveSet.has(key)) {
91
+ sanitized[key] = value;
92
+ }
93
+ }
94
+ return sanitized;
95
+ });
96
+ }
97
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../src/security.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,yBAAyB;AACzB,MAAM,gBAAgB,GAAG,GAAG,CAAC;AAE7B,eAAe;AACf,MAAM,YAAY,GAAG;IACnB,uBAAuB,EAAqB,MAAM;IAClD,4CAA4C,EAAE,cAAc;IAC5D,kDAAkD,EAAE,QAAQ;IAC5D,+BAA+B,EAAa,eAAe;CAC5D,CAAC;AAEF,MAAM,UAAU,aAAa,CAAC,KAAa;IACzC,IAAI,KAAK,CAAC,MAAM,GAAG,gBAAgB,EAAE,CAAC;QACpC,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,OAAO,EAAE,mCAAmC,gBAAgB,aAAa,CAAC,CAAC;IACpH,CAAC;IACD,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,UAAU,CAAC,gBAAgB,EAAE,OAAO,EAAE,uBAAuB,CAAC,CAAC;IAC3E,CAAC;IACD,gBAAgB;IAChB,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;QACnC,IAAI,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;YACxB,MAAM,IAAI,UAAU,CAAC,oBAAoB,EAAE,OAAO,EAAE,+CAA+C,CAAC,CAAC;QACvG,CAAC;IACH,CAAC;IACD,6BAA6B;IAC7B,MAAM,MAAM,GAAG,iBAAiB,CAAC,KAAK,CAAC,CAAC;IACxC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACjB,MAAM,IAAI,UAAU,CAAC,yBAAyB,EAAE,OAAO,EAAE,sCAAsC,CAAC,CAAC;IACnG,CAAC;AACH,CAAC;AASD,MAAM,kBAAkB,GAA0C;IAChE,EAAE,OAAO,EAAE,4CAA4C,EAAE,MAAM,EAAE,8BAA8B,EAAE;IACjG,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,2BAA2B,EAAE;IACpE,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,EAAE,qBAAqB,EAAE;IAC9D,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,EAAE,kBAAkB,EAAE;IACpD,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,oBAAoB,EAAE;IAC/D,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,oBAAoB,EAAE;IAC/D,EAAE,OAAO,EAAE,sBAAsB,EAAE,MAAM,EAAE,oBAAoB,EAAE;IACjE,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,mBAAmB,EAAE;IACtD,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,sBAAsB,EAAE;IAC5D,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,mBAAmB,EAAE;IACtD,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,oBAAoB,EAAE;IACxD,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,0BAA0B,EAAE;IAClE,EAAE,OAAO,EAAE,yBAAyB,EAAE,MAAM,EAAE,8BAA8B,EAAE;IAC9E,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,qBAAqB,EAAE;IAC5D,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,mBAAmB,EAAE;IACxD,EAAE,OAAO,EAAE,8BAA8B,EAAE,MAAM,EAAE,4BAA4B,EAAE;IACjF,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,sBAAsB,EAAE;IAC5D,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,mBAAmB,EAAE;IAC7D,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,uBAAuB,EAAE;IAC3D,EAAE,OAAO,EAAE,8BAA8B,EAAE,MAAM,EAAE,2BAA2B,EAAE;IAChF,EAAE,OAAO,EAAE,2BAA2B,EAAE,MAAM,EAAE,6BAA6B,EAAE;IAC/E,EAAE,OAAO,EAAE,mBAAmB,EAAE,MAAM,EAAE,cAAc,EAAE;IACxD,EAAE,OAAO,EAAE,cAAc,EAAE,MAAM,EAAE,iBAAiB,EAAE;IACtD,EAAE,OAAO,EAAE,wCAAwC,EAAE,MAAM,EAAE,6BAA6B,EAAE;IAC5F,EAAE,OAAO,EAAE,uCAAuC,EAAE,MAAM,EAAE,6BAA6B,EAAE;IAC3F,EAAE,OAAO,EAAE,qBAAqB,EAAE,MAAM,EAAE,iBAAiB,EAAE;IAC7D,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,wBAAwB,EAAE;IAC3D,EAAE,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,oBAAoB,EAAE;IACxD,EAAE,OAAO,EAAE,eAAe,EAAE,MAAM,EAAE,mBAAmB,EAAE;IACzD,EAAE,OAAO,EAAE,2CAA2C,EAAE,MAAM,EAAE,mBAAmB,EAAE;IACrF,EAAE,OAAO,EAAE,wBAAwB,EAAE,MAAM,EAAE,8BAA8B,EAAE;IAC7E,EAAE,OAAO,EAAE,2BAA2B,EAAE,MAAM,EAAE,uBAAuB,EAAE;IACzE,EAAE,OAAO,EAAE,gBAAgB,EAAE,MAAM,EAAE,mBAAmB,EAAE;IAC1D,EAAE,OAAO,EAAE,oBAAoB,EAAE,MAAM,EAAE,kBAAkB,EAAE;CAC9D,CAAC;AAEF,MAAM,UAAU,iBAAiB,CAAC,KAAa;IAC7C,qCAAqC;IACrC,MAAM,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;IAE3C,KAAK,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,kBAAkB,EAAE,CAAC;QACrD,IAAI,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;YAC7B,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QACnD,CAAC;IACH,CAAC;IAED,oEAAoE;IACpE,MAAM,SAAS,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,0GAA0G,CAAC,IAAI,EAAE,CAAC,CAAC,MAAM,CAAC;IAC9J,MAAM,SAAS,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC;IACjD,IAAI,SAAS,GAAG,CAAC,IAAI,SAAS,GAAG,SAAS,GAAG,GAAG,EAAE,CAAC;QACjD,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,EAAE,MAAM,EAAE,wBAAwB,EAAE,CAAC;IAC5E,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;AAC1C,CAAC;AAED,4BAA4B;AAC5B,MAAM,UAAU,cAAc,CAC5B,IAA+B,EAC/B,gBAAmC;IAEnC,IAAI,gBAAgB,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC/C,MAAM,YAAY,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC/C,OAAO,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE;QACpB,MAAM,SAAS,GAA4B,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;YAC/C,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBAC3B,SAAS,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;YACzB,CAAC;QACH,CAAC;QACD,OAAO,SAAS,CAAC;IACnB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { ValidatedSmartPlan, ExecutionContext, CompiledSQL } from './types.js';
2
+ declare function quoteIdentifier(id: string): string;
3
+ export { quoteIdentifier };
4
+ export declare function compileSQL(plan: ValidatedSmartPlan, ctx: ExecutionContext): CompiledSQL;
5
+ //# sourceMappingURL=sql-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-builder.d.ts","sourceRoot":"","sources":["../src/sql-builder.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAIpF,iBAAS,eAAe,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAM3C;AAED,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,wBAAgB,UAAU,CAAC,IAAI,EAAE,kBAAkB,EAAE,GAAG,EAAE,gBAAgB,GAAG,WAAW,CAuGvF"}
@@ -0,0 +1,121 @@
1
+ import { InferError } from './errors.js';
2
+ // Use pg driver's native escapeIdentifier pattern
3
+ // Since we can't import pg at compile time (it's optional), we implement the safe quoting
4
+ function quoteIdentifier(id) {
5
+ // Validate: identifiers must be alphanumeric + underscore (no SQL injection via identifiers)
6
+ if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(id)) {
7
+ throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid identifier: ${id}`);
8
+ }
9
+ return `"${id.replace(/"/g, '""')}"`;
10
+ }
11
+ export { quoteIdentifier };
12
+ export function compileSQL(plan, ctx) {
13
+ const params = [];
14
+ let paramIdx = 1;
15
+ // ── Build SELECT clause ──
16
+ let selectCols;
17
+ if (plan.metrics.length > 0) {
18
+ // Aggregate query: groupBy columns + metric expressions
19
+ const groupCols = plan.groupBy.map(col => quoteIdentifier(col));
20
+ const metricCols = plan.metrics.map(m => `${m.expression} AS ${quoteIdentifier(m.id)}`);
21
+ selectCols = [...groupCols, ...metricCols];
22
+ }
23
+ else if (plan.select.length > 0) {
24
+ // Raw row query: specific columns
25
+ selectCols = plan.select.map(col => quoteIdentifier(col));
26
+ }
27
+ else {
28
+ // Fallback: select all selectable columns from dataset
29
+ selectCols = plan.dataset.selectableColumns.map(col => quoteIdentifier(col));
30
+ }
31
+ if (selectCols.length === 0) {
32
+ throw new InferError('PLAN_INVALID', 'compiler', 'No columns to select');
33
+ }
34
+ // ── Build FROM clause ──
35
+ const fromTable = `${quoteIdentifier(plan.dataset.table.schema)}.${quoteIdentifier(plan.dataset.table.name)}`;
36
+ // ── Build WHERE clause ──
37
+ const whereParts = [];
38
+ // Tenant isolation (ALWAYS injected server-side)
39
+ params.push(ctx.tenantId);
40
+ whereParts.push(`"tenant_id" = $${paramIdx++}`);
41
+ // Location scoping (optional)
42
+ if (ctx.locationIds && ctx.locationIds.length > 0) {
43
+ params.push(ctx.locationIds);
44
+ whereParts.push(`"location_id" = ANY($${paramIdx++})`);
45
+ }
46
+ // Date range
47
+ if (plan.dateRange && plan.dataset.dateColumn) {
48
+ params.push(plan.dateRange.startDate);
49
+ whereParts.push(`${quoteIdentifier(plan.dataset.dateColumn)} >= $${paramIdx++}`);
50
+ params.push(plan.dateRange.endDate);
51
+ whereParts.push(`${quoteIdentifier(plan.dataset.dateColumn)} <= $${paramIdx++}`);
52
+ }
53
+ // User filters (parameterized)
54
+ for (const filter of plan.filters) {
55
+ const col = quoteIdentifier(filter.field);
56
+ const op = sanitizeOperator(filter.op);
57
+ if (op === 'IN' || op === 'NOT IN') {
58
+ // Array parameter
59
+ params.push(filter.value);
60
+ whereParts.push(`${col} ${op === 'NOT IN' ? 'NOT ' : ''}= ANY($${paramIdx++})`);
61
+ }
62
+ else if (op === 'LIKE' || op === 'ILIKE') {
63
+ params.push(filter.value);
64
+ whereParts.push(`${col} ${op} $${paramIdx++}`);
65
+ }
66
+ else {
67
+ params.push(filter.value);
68
+ whereParts.push(`${col} ${op} $${paramIdx++}`);
69
+ }
70
+ }
71
+ // Entity filters (resolved IDs)
72
+ for (const entity of plan.resolvedEntities) {
73
+ if (entity.resolvedIds.length > 0) {
74
+ const col = quoteIdentifier(entity.outputColumn);
75
+ params.push(entity.resolvedIds);
76
+ whereParts.push(`${col} = ANY($${paramIdx++})`);
77
+ }
78
+ }
79
+ const whereClause = whereParts.length > 0 ? whereParts.join(' AND ') : '1=1';
80
+ // ── Build GROUP BY ──
81
+ const groupBySql = plan.groupBy.length > 0
82
+ ? `GROUP BY ${plan.groupBy.map(col => quoteIdentifier(col)).join(', ')}`
83
+ : '';
84
+ // ── Build ORDER BY (direction whitelisted to ASC/DESC only) ──
85
+ const orderBySql = plan.orderBy.length > 0
86
+ ? `ORDER BY ${plan.orderBy.map(o => `${quoteIdentifier(o.field)} ${o.dir === 'desc' ? 'DESC' : 'ASC'}`).join(', ')}`
87
+ : '';
88
+ // ── LIMIT (always applied) ──
89
+ params.push(plan.limit);
90
+ const limitSql = `LIMIT $${paramIdx++}`;
91
+ // ── Assemble ──
92
+ const sql = [
93
+ `SELECT ${selectCols.join(', ')}`,
94
+ `FROM ${fromTable}`,
95
+ `WHERE ${whereClause}`,
96
+ groupBySql,
97
+ orderBySql,
98
+ limitSql,
99
+ ].filter(Boolean).join('\n');
100
+ return { sql, params };
101
+ }
102
+ function sanitizeOperator(op) {
103
+ const ALLOWED_OPS = {
104
+ '=': '=',
105
+ '!=': '!=',
106
+ '>': '>',
107
+ '<': '<',
108
+ '>=': '>=',
109
+ '<=': '<=',
110
+ 'in': 'IN',
111
+ 'not_in': 'NOT IN',
112
+ 'like': 'LIKE',
113
+ 'ilike': 'ILIKE',
114
+ };
115
+ const sanitized = ALLOWED_OPS[op];
116
+ if (!sanitized) {
117
+ throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid operator: ${op}`);
118
+ }
119
+ return sanitized;
120
+ }
121
+ //# sourceMappingURL=sql-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-builder.js","sourceRoot":"","sources":["../src/sql-builder.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface SQLValidationOptions {
2
+ allowedTables?: string[];
3
+ requireTenantFilter?: boolean;
4
+ tenantColumn?: string;
5
+ }
6
+ export declare function validateSQL(sql: string, options?: SQLValidationOptions): void;
7
+ export declare function validateMetricExpression(expr: string): boolean;
8
+ //# sourceMappingURL=sql-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-validator.d.ts","sourceRoot":"","sources":["../src/sql-validator.ts"],"names":[],"mappings":"AA8CA,MAAM,WAAW,oBAAoB;IACnC,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,oBAAyB,GAAG,IAAI,CAgEjF;AA2CD,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAyB9D"}
@@ -0,0 +1,160 @@
1
+ import { InferError } from './errors.js';
2
+ // Function ALLOWLIST (not blocklist) - exhaustive list from spec
3
+ const ALLOWED_FUNCTIONS = new Set([
4
+ // Aggregates
5
+ 'sum', 'avg', 'count', 'min', 'max',
6
+ // Math
7
+ 'round', 'ceil', 'floor', 'abs', 'sign', 'mod', 'power',
8
+ // Date/Time
9
+ 'date_trunc', 'extract', 'now', 'current_date', 'current_timestamp', 'age',
10
+ // String
11
+ 'lower', 'upper', 'trim', 'length', 'substring', 'concat', 'left', 'right',
12
+ // Type
13
+ 'cast', 'coalesce', 'nullif', 'greatest', 'least',
14
+ // Format
15
+ 'to_char', 'to_date', 'to_number', 'to_timestamp',
16
+ ]);
17
+ // Forbidden SQL keywords that should NEVER appear
18
+ const FORBIDDEN_KEYWORDS = new Set([
19
+ 'insert', 'update', 'delete', 'drop', 'alter', 'create', 'truncate',
20
+ 'grant', 'revoke', 'copy', 'execute', 'call', 'reset', 'prepare',
21
+ 'deallocate', 'listen', 'notify', 'vacuum', 'analyze', 'load',
22
+ 'outfile', 'dumpfile', 'handler',
23
+ ]);
24
+ // System catalogs that must never be referenced
25
+ const FORBIDDEN_TABLES = [
26
+ 'information_schema.',
27
+ 'pg_catalog.',
28
+ 'pg_stat_',
29
+ 'pg_shadow',
30
+ 'pg_authid',
31
+ 'pg_roles',
32
+ ];
33
+ // Forbidden function names (dangerous postgres functions)
34
+ const FORBIDDEN_FUNCTIONS = new Set([
35
+ 'pg_sleep', 'pg_read_file', 'pg_ls_dir', 'pg_stat_file',
36
+ 'dblink', 'lo_import', 'lo_export',
37
+ 'pg_terminate_backend', 'pg_cancel_backend', 'pg_advisory_lock',
38
+ 'current_setting', 'set_config',
39
+ 'generate_series', 'unnest',
40
+ 'xml_parse', 'xpath',
41
+ ]);
42
+ export function validateSQL(sql, options = {}) {
43
+ const normalized = sql.toLowerCase().trim();
44
+ // 1. Single statement only
45
+ const statementCount = sql.split(';').filter(s => s.trim().length > 0).length;
46
+ if (statementCount > 1) {
47
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'Multiple statements not allowed');
48
+ }
49
+ // 2. Must be SELECT or WITH (CTE)
50
+ if (!normalized.startsWith('select') && !normalized.startsWith('with')) {
51
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'Only SELECT statements are allowed');
52
+ }
53
+ // 3. Check forbidden keywords
54
+ for (const keyword of FORBIDDEN_KEYWORDS) {
55
+ // Use word boundary matching to avoid false positives
56
+ const regex = new RegExp(`\\b${keyword}\\b`, 'i');
57
+ if (regex.test(normalized)) {
58
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'Forbidden SQL keyword detected');
59
+ }
60
+ }
61
+ // 4. Check for system catalog references
62
+ for (const table of FORBIDDEN_TABLES) {
63
+ if (normalized.includes(table.toLowerCase())) {
64
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'System catalog access not allowed');
65
+ }
66
+ }
67
+ // 5. Check for forbidden functions
68
+ for (const func of FORBIDDEN_FUNCTIONS) {
69
+ const regex = new RegExp(`\\b${func}\\s*\\(`, 'i');
70
+ if (regex.test(normalized)) {
71
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'Forbidden function detected');
72
+ }
73
+ }
74
+ // 6. Check for dangerous patterns
75
+ if (/\bfor\s+(update|share)\b/i.test(sql)) {
76
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'FOR UPDATE/SHARE not allowed');
77
+ }
78
+ if (/\breturning\b/i.test(sql)) {
79
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'RETURNING clause not allowed');
80
+ }
81
+ if (/\binto\s+(outfile|dumpfile)\b/i.test(sql)) {
82
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'INTO OUTFILE/DUMPFILE not allowed');
83
+ }
84
+ // 7. Validate table allowlist if provided
85
+ if (options.allowedTables && options.allowedTables.length > 0) {
86
+ validateTableAllowlist(normalized, options.allowedTables);
87
+ }
88
+ // 8. Validate functions against allowlist
89
+ validateFunctions(normalized);
90
+ // 9. Check for tenant filter if required
91
+ if (options.requireTenantFilter) {
92
+ const tenantCol = options.tenantColumn || 'tenant_id';
93
+ if (!normalized.includes(tenantCol)) {
94
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'Missing required tenant filter');
95
+ }
96
+ }
97
+ }
98
+ function validateFunctions(sql) {
99
+ // Extract function calls using regex (matches function_name followed by open paren)
100
+ const functionPattern = /\b([a-z_][a-z0-9_]*)\s*\(/gi;
101
+ let match;
102
+ while ((match = functionPattern.exec(sql)) !== null) {
103
+ const funcName = match[1].toLowerCase();
104
+ // Skip SQL keywords that look like functions
105
+ const sqlKeywords = new Set(['select', 'from', 'where', 'and', 'or', 'not', 'in', 'exists', 'case', 'when', 'then', 'else', 'end', 'as', 'on', 'join', 'left', 'right', 'inner', 'outer', 'cross', 'having', 'group', 'order', 'with', 'over', 'partition', 'between', 'like', 'ilike', 'any', 'all', 'some', 'values']);
106
+ if (sqlKeywords.has(funcName))
107
+ continue;
108
+ // Check against forbidden functions first (more specific error)
109
+ if (FORBIDDEN_FUNCTIONS.has(funcName)) {
110
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'Forbidden function detected');
111
+ }
112
+ // Check against allowlist
113
+ if (!ALLOWED_FUNCTIONS.has(funcName)) {
114
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', `Function not allowed: ${funcName}`);
115
+ }
116
+ }
117
+ }
118
+ function validateTableAllowlist(sql, allowedTables) {
119
+ // Extract table references from FROM and JOIN clauses
120
+ const fromPattern = /\b(?:from|join)\s+([a-z_][a-z0-9_.]*)/gi;
121
+ let match;
122
+ const allowedSet = new Set(allowedTables.map(t => t.toLowerCase()));
123
+ while ((match = fromPattern.exec(sql)) !== null) {
124
+ const tableName = match[1].toLowerCase().replace(/^"|"$/g, '');
125
+ if (!allowedSet.has(tableName)) {
126
+ throw new InferError('SQL_VALIDATION_FAILED', 'sql_check', 'Table not in allowlist');
127
+ }
128
+ }
129
+ }
130
+ // Validate metric expressions at catalog load time
131
+ const SAFE_METRIC_REGEX = /^(SUM|AVG|COUNT|MIN|MAX|ROUND|ABS|CEIL|FLOOR|COALESCE|NULLIF|CAST)\s*\(/i;
132
+ export function validateMetricExpression(expr) {
133
+ // Simple metric expressions: FUNC(column)
134
+ const simplePattern = /^(SUM|AVG|COUNT|MIN|MAX|ROUND|ABS|CEIL|FLOOR)\s*\(\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\)$/i;
135
+ if (simplePattern.test(expr))
136
+ return true;
137
+ // COUNT(*) special case
138
+ if (/^COUNT\s*\(\s*\*\s*\)$/i.test(expr))
139
+ return true;
140
+ // Nested single-level: ROUND(AVG(column))
141
+ const nestedPattern = /^(ROUND|CEIL|FLOOR|ABS)\s*\(\s*(SUM|AVG|COUNT|MIN|MAX)\s*\(\s*[a-zA-Z_][a-zA-Z0-9_]*\s*\)\s*(,\s*\d+\s*)?\)$/i;
142
+ if (nestedPattern.test(expr))
143
+ return true;
144
+ // ROUND(expr, precision)
145
+ const roundPattern = /^ROUND\s*\(\s*[a-zA-Z_][a-zA-Z0-9_]*\s*,\s*\d+\s*\)$/i;
146
+ if (roundPattern.test(expr))
147
+ return true;
148
+ // Check for any forbidden patterns
149
+ if (FORBIDDEN_FUNCTIONS.has(expr.split('(')[0].trim().toLowerCase()))
150
+ return false;
151
+ if (/;|--|\/\*|\*\//i.test(expr))
152
+ return false;
153
+ if (/\b(SELECT|FROM|WHERE|JOIN|INSERT|UPDATE|DELETE|DROP)\b/i.test(expr))
154
+ return false;
155
+ // If starts with an allowed function, tentatively accept
156
+ if (SAFE_METRIC_REGEX.test(expr.trim()))
157
+ return true;
158
+ return false;
159
+ }
160
+ //# sourceMappingURL=sql-validator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sql-validator.js","sourceRoot":"","sources":["../src/sql-validator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ import type { InferConnector, ColumnMeta, IntrospectedSchema, SmartQueryPlan, LLMMessage, LLMOptions } from './types.js';
2
+ import type { LLMProvider } from './llm.js';
3
+ export declare class MockConnector implements InferConnector {
4
+ private tables;
5
+ constructor(config: {
6
+ tables: Record<string, Record<string, unknown>[]>;
7
+ });
8
+ execute(sql: string, _params: unknown[]): Promise<{
9
+ rows: Record<string, unknown>[];
10
+ columns: ColumnMeta[];
11
+ }>;
12
+ introspect(): Promise<IntrospectedSchema>;
13
+ close(): Promise<void>;
14
+ }
15
+ export declare class MockLLM implements LLMProvider {
16
+ private responses;
17
+ constructor(config: {
18
+ responses: Record<string, SmartQueryPlan>;
19
+ });
20
+ chat(messages: LLMMessage[], _options?: LLMOptions): Promise<string>;
21
+ }
22
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.d.ts","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,cAAc,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACzH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5C,qBAAa,aAAc,YAAW,cAAc;IAClD,OAAO,CAAC,MAAM,CAA4C;gBAE9C,MAAM,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC,CAAA;KAAE;IAQnE,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;QAAC,OAAO,EAAE,UAAU,EAAE,CAAA;KAAE,CAAC;IAa7G,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAuBzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAC7B;AAGD,qBAAa,OAAQ,YAAW,WAAW;IACzC,OAAO,CAAC,SAAS,CAAiC;gBAEtC,MAAM,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;KAAE;IAO3D,IAAI,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,QAAQ,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC;CA0B3E"}
@@ -0,0 +1,78 @@
1
+ // ── MockConnector ──
2
+ export class MockConnector {
3
+ tables;
4
+ constructor(config) {
5
+ // Safety: MockConnector must NEVER be used in production
6
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
7
+ throw new Error('MockConnector cannot be used in NODE_ENV=production');
8
+ }
9
+ this.tables = config.tables;
10
+ }
11
+ async execute(sql, _params) {
12
+ // Extract table name from SQL (basic parser for testing)
13
+ const fromMatch = sql.match(/FROM\s+"?(\w+)"?/i);
14
+ const tableName = fromMatch?.[1] ?? '';
15
+ const rows = this.tables[tableName] ?? [];
16
+ const columns = rows.length > 0
17
+ ? Object.keys(rows[0]).map(name => ({ name, type: 'text' }))
18
+ : [];
19
+ return { rows, columns };
20
+ }
21
+ async introspect() {
22
+ return {
23
+ tables: Object.entries(this.tables).map(([name, rows]) => ({
24
+ schema: 'public',
25
+ name,
26
+ columns: rows.length > 0
27
+ ? Object.keys(rows[0]).map(col => ({
28
+ name: col,
29
+ dataType: typeof rows[0][col] === 'number' ? 'numeric' : 'text',
30
+ typeCategory: typeof rows[0][col] === 'number' ? 'N' : 'S',
31
+ notNull: false,
32
+ defaultValue: null,
33
+ comment: null,
34
+ }))
35
+ : [],
36
+ estimatedRows: rows.length,
37
+ foreignKeys: [],
38
+ indexes: [],
39
+ })),
40
+ enums: {},
41
+ };
42
+ }
43
+ async close() { }
44
+ }
45
+ // ── MockLLM ──
46
+ export class MockLLM {
47
+ responses;
48
+ constructor(config) {
49
+ if (typeof process !== 'undefined' && process.env?.NODE_ENV === 'production') {
50
+ throw new Error('MockLLM cannot be used in NODE_ENV=production');
51
+ }
52
+ this.responses = config.responses;
53
+ }
54
+ async chat(messages, _options) {
55
+ // Find the user message
56
+ const userMsg = messages.find(m => m.role === 'user')?.content ?? '';
57
+ // Check for exact match first
58
+ if (this.responses[userMsg]) {
59
+ return JSON.stringify(this.responses[userMsg]);
60
+ }
61
+ // Check for partial match
62
+ for (const [key, plan] of Object.entries(this.responses)) {
63
+ if (userMsg.toLowerCase().includes(key.toLowerCase())) {
64
+ return JSON.stringify(plan);
65
+ }
66
+ }
67
+ // Default response
68
+ return JSON.stringify({
69
+ dataset: 'unknown',
70
+ select: [],
71
+ metrics: [],
72
+ groupBy: [],
73
+ filters: [],
74
+ answerMode: 'rows',
75
+ });
76
+ }
77
+ }
78
+ //# sourceMappingURL=testing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"testing.js","sourceRoot":"","sources":["../src/testing.ts"],"names":[],"mappings":"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"}