@reasoningco/infer 0.1.4 → 0.1.5
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/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +58 -11
- package/dist/catalog.js.map +1 -1
- package/dist/connectors.d.ts +2 -0
- package/dist/connectors.d.ts.map +1 -1
- package/dist/connectors.js +22 -5
- package/dist/connectors.js.map +1 -1
- package/dist/dates.d.ts.map +1 -1
- package/dist/dates.js +5 -1
- package/dist/dates.js.map +1 -1
- package/dist/entity-resolver.d.ts.map +1 -1
- package/dist/entity-resolver.js +3 -1
- package/dist/entity-resolver.js.map +1 -1
- package/dist/errors.js +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +19 -11
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts.map +1 -1
- package/dist/llm.js +17 -5
- package/dist/llm.js.map +1 -1
- package/dist/query-validator.d.ts.map +1 -1
- package/dist/query-validator.js +7 -0
- package/dist/query-validator.js.map +1 -1
- package/dist/schemas.d.ts +50 -50
- package/dist/schemas.js +10 -10
- package/dist/schemas.js.map +1 -1
- package/dist/sql-builder.d.ts.map +1 -1
- package/dist/sql-builder.js +78 -33
- package/dist/sql-builder.js.map +1 -1
- package/dist/sql-validator.d.ts.map +1 -1
- package/dist/sql-validator.js +18 -8
- package/dist/sql-validator.js.map +1 -1
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/schemas.d.ts
CHANGED
|
@@ -2,15 +2,15 @@ import { z } from "zod";
|
|
|
2
2
|
export declare const FilterSchema: z.ZodObject<{
|
|
3
3
|
field: z.ZodString;
|
|
4
4
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
5
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
5
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
6
6
|
}, "strip", z.ZodTypeAny, {
|
|
7
|
-
value: string | number | boolean | (string | number)[];
|
|
8
7
|
field: string;
|
|
9
8
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
9
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
10
10
|
}, {
|
|
11
|
-
value: string | number | boolean | (string | number)[];
|
|
12
11
|
field: string;
|
|
13
12
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
13
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
14
14
|
}>;
|
|
15
15
|
export declare const EntityFilterSchema: z.ZodObject<{
|
|
16
16
|
resolver: z.ZodString;
|
|
@@ -33,15 +33,15 @@ export declare const ExistsFilterSchema: z.ZodObject<{
|
|
|
33
33
|
filters: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
34
34
|
field: z.ZodString;
|
|
35
35
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
36
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
36
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
37
37
|
}, "strip", z.ZodTypeAny, {
|
|
38
|
-
value: string | number | boolean | (string | number)[];
|
|
39
38
|
field: string;
|
|
40
39
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
40
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
41
41
|
}, {
|
|
42
|
-
value: string | number | boolean | (string | number)[];
|
|
43
42
|
field: string;
|
|
44
43
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
44
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
45
45
|
}>, "many">>>;
|
|
46
46
|
}, "strip", z.ZodTypeAny, {
|
|
47
47
|
mode: "not_exists" | "exists";
|
|
@@ -49,9 +49,9 @@ export declare const ExistsFilterSchema: z.ZodObject<{
|
|
|
49
49
|
joinColumn: string;
|
|
50
50
|
foreignColumn: string;
|
|
51
51
|
filters: {
|
|
52
|
-
value: string | number | boolean | (string | number)[];
|
|
53
52
|
field: string;
|
|
54
53
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
54
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
55
55
|
}[];
|
|
56
56
|
}, {
|
|
57
57
|
mode: "not_exists" | "exists";
|
|
@@ -59,9 +59,9 @@ export declare const ExistsFilterSchema: z.ZodObject<{
|
|
|
59
59
|
joinColumn: string;
|
|
60
60
|
foreignColumn: string;
|
|
61
61
|
filters?: {
|
|
62
|
-
value: string | number | boolean | (string | number)[];
|
|
63
62
|
field: string;
|
|
64
63
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
64
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
65
65
|
}[] | undefined;
|
|
66
66
|
}>;
|
|
67
67
|
export declare const DateRangeSchema: z.ZodObject<{
|
|
@@ -92,40 +92,44 @@ export declare const OrderBySchema: z.ZodObject<{
|
|
|
92
92
|
}>;
|
|
93
93
|
export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
94
94
|
dataset: z.ZodString;
|
|
95
|
-
select: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many"
|
|
96
|
-
metrics: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many"
|
|
97
|
-
groupBy: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many"
|
|
95
|
+
select: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>, string[], unknown>;
|
|
96
|
+
metrics: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>, string[], unknown>;
|
|
97
|
+
groupBy: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodString, "many">>>, string[], unknown>;
|
|
98
98
|
timeBucket: z.ZodEffects<z.ZodOptional<z.ZodEnum<["hour", "day", "week", "month", "quarter", "year"]>>, "month" | "day" | "week" | "quarter" | "year" | "hour" | undefined, unknown>;
|
|
99
99
|
extractBucket: z.ZodEffects<z.ZodOptional<z.ZodEnum<["hour_of_day", "day_of_week", "month_of_year", "quarter", "year", "week"]>>, "week" | "quarter" | "year" | "hour_of_day" | "day_of_week" | "month_of_year" | undefined, unknown>;
|
|
100
|
-
filters: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
100
|
+
filters: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
101
101
|
field: z.ZodString;
|
|
102
102
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
103
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
103
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
104
104
|
}, "strip", z.ZodTypeAny, {
|
|
105
|
-
value: string | number | boolean | (string | number)[];
|
|
106
105
|
field: string;
|
|
107
106
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
107
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
108
108
|
}, {
|
|
109
|
-
value: string | number | boolean | (string | number)[];
|
|
110
109
|
field: string;
|
|
111
110
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
112
|
-
|
|
111
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
112
|
+
}>, "many">>>, {
|
|
113
|
+
field: string;
|
|
114
|
+
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
115
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
116
|
+
}[], unknown>;
|
|
113
117
|
having: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
114
118
|
field: z.ZodString;
|
|
115
119
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
116
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
120
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
117
121
|
}, "strip", z.ZodTypeAny, {
|
|
118
|
-
value: string | number | boolean | (string | number)[];
|
|
119
122
|
field: string;
|
|
120
123
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
124
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
121
125
|
}, {
|
|
122
|
-
value: string | number | boolean | (string | number)[];
|
|
123
126
|
field: string;
|
|
124
127
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
128
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
125
129
|
}>, "many">>>, {
|
|
126
|
-
value: string | number | boolean | (string | number)[];
|
|
127
130
|
field: string;
|
|
128
131
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
132
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
129
133
|
}[], unknown>;
|
|
130
134
|
existsFilters: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
131
135
|
mode: z.ZodEnum<["exists", "not_exists"]>;
|
|
@@ -135,15 +139,15 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
135
139
|
filters: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
136
140
|
field: z.ZodString;
|
|
137
141
|
op: z.ZodEnum<["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]>;
|
|
138
|
-
value: z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]
|
|
142
|
+
value: z.ZodOptional<z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean, z.ZodArray<z.ZodUnion<[z.ZodString, z.ZodNumber]>, "many">]>>;
|
|
139
143
|
}, "strip", z.ZodTypeAny, {
|
|
140
|
-
value: string | number | boolean | (string | number)[];
|
|
141
144
|
field: string;
|
|
142
145
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
146
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
143
147
|
}, {
|
|
144
|
-
value: string | number | boolean | (string | number)[];
|
|
145
148
|
field: string;
|
|
146
149
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
150
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
147
151
|
}>, "many">>>;
|
|
148
152
|
}, "strip", z.ZodTypeAny, {
|
|
149
153
|
mode: "not_exists" | "exists";
|
|
@@ -151,9 +155,9 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
151
155
|
joinColumn: string;
|
|
152
156
|
foreignColumn: string;
|
|
153
157
|
filters: {
|
|
154
|
-
value: string | number | boolean | (string | number)[];
|
|
155
158
|
field: string;
|
|
156
159
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
160
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
157
161
|
}[];
|
|
158
162
|
}, {
|
|
159
163
|
mode: "not_exists" | "exists";
|
|
@@ -161,9 +165,9 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
161
165
|
joinColumn: string;
|
|
162
166
|
foreignColumn: string;
|
|
163
167
|
filters?: {
|
|
164
|
-
value: string | number | boolean | (string | number)[];
|
|
165
168
|
field: string;
|
|
166
169
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
170
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
167
171
|
}[] | undefined;
|
|
168
172
|
}>, "many">>>, {
|
|
169
173
|
mode: "not_exists" | "exists";
|
|
@@ -171,12 +175,12 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
171
175
|
joinColumn: string;
|
|
172
176
|
foreignColumn: string;
|
|
173
177
|
filters: {
|
|
174
|
-
value: string | number | boolean | (string | number)[];
|
|
175
178
|
field: string;
|
|
176
179
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
180
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
177
181
|
}[];
|
|
178
182
|
}[], unknown>;
|
|
179
|
-
entityFilters: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
183
|
+
entityFilters: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
180
184
|
resolver: z.ZodString;
|
|
181
185
|
input: z.ZodString;
|
|
182
186
|
outputColumn: z.ZodString;
|
|
@@ -188,7 +192,11 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
188
192
|
input: string;
|
|
189
193
|
resolver: string;
|
|
190
194
|
outputColumn: string;
|
|
191
|
-
}>, "many"
|
|
195
|
+
}>, "many">>>, {
|
|
196
|
+
input: string;
|
|
197
|
+
resolver: string;
|
|
198
|
+
outputColumn: string;
|
|
199
|
+
}[], unknown>;
|
|
192
200
|
dateRange: z.ZodEffects<z.ZodOptional<z.ZodObject<{
|
|
193
201
|
mode: z.ZodEnum<["relative", "explicit", "latest", "none"]>;
|
|
194
202
|
phrase: z.ZodOptional<z.ZodString>;
|
|
@@ -210,7 +218,7 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
210
218
|
endDate?: string | undefined;
|
|
211
219
|
phrase?: string | undefined;
|
|
212
220
|
} | undefined, unknown>;
|
|
213
|
-
orderBy: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
221
|
+
orderBy: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
214
222
|
field: z.ZodString;
|
|
215
223
|
dir: z.ZodDefault<z.ZodEnum<["asc", "desc"]>>;
|
|
216
224
|
}, "strip", z.ZodTypeAny, {
|
|
@@ -219,22 +227,25 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
219
227
|
}, {
|
|
220
228
|
field: string;
|
|
221
229
|
dir?: "desc" | "asc" | undefined;
|
|
222
|
-
}>, "many"
|
|
230
|
+
}>, "many">>>, {
|
|
231
|
+
field: string;
|
|
232
|
+
dir: "desc" | "asc";
|
|
233
|
+
}[], unknown>;
|
|
223
234
|
limit: z.ZodEffects<z.ZodOptional<z.ZodNumber>, number | undefined, unknown>;
|
|
224
235
|
answerMode: z.ZodEffects<z.ZodDefault<z.ZodOptional<z.ZodEnum<["rows", "aggregate_table", "scalar"]>>>, "aggregate_table" | "rows" | "scalar", unknown>;
|
|
225
236
|
compareMode: z.ZodEffects<z.ZodOptional<z.ZodEnum<["previous_period", "year_over_year"]>>, "previous_period" | "year_over_year" | undefined, unknown>;
|
|
226
237
|
}, "strip", z.ZodTypeAny, {
|
|
227
238
|
select: string[];
|
|
228
239
|
having: {
|
|
229
|
-
value: string | number | boolean | (string | number)[];
|
|
230
240
|
field: string;
|
|
231
241
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
242
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
232
243
|
}[];
|
|
233
244
|
dataset: string;
|
|
234
245
|
filters: {
|
|
235
|
-
value: string | number | boolean | (string | number)[];
|
|
236
246
|
field: string;
|
|
237
247
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
248
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
238
249
|
}[];
|
|
239
250
|
metrics: string[];
|
|
240
251
|
groupBy: string[];
|
|
@@ -244,9 +255,9 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
244
255
|
joinColumn: string;
|
|
245
256
|
foreignColumn: string;
|
|
246
257
|
filters: {
|
|
247
|
-
value: string | number | boolean | (string | number)[];
|
|
248
258
|
field: string;
|
|
249
259
|
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
260
|
+
value?: string | number | boolean | (string | number)[] | undefined;
|
|
250
261
|
}[];
|
|
251
262
|
}[];
|
|
252
263
|
entityFilters: {
|
|
@@ -271,28 +282,17 @@ export declare const SmartQueryPlanSchema: z.ZodObject<{
|
|
|
271
282
|
compareMode?: "previous_period" | "year_over_year" | undefined;
|
|
272
283
|
}, {
|
|
273
284
|
dataset: string;
|
|
274
|
-
select?:
|
|
285
|
+
select?: unknown;
|
|
275
286
|
having?: unknown;
|
|
276
|
-
filters?:
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
op: "=" | ">" | "!=" | "<" | ">=" | "<=" | "in" | "not_in" | "like" | "ilike" | "is_null" | "is_not_null";
|
|
280
|
-
}[] | undefined;
|
|
281
|
-
metrics?: string[] | undefined;
|
|
282
|
-
groupBy?: string[] | undefined;
|
|
287
|
+
filters?: unknown;
|
|
288
|
+
metrics?: unknown;
|
|
289
|
+
groupBy?: unknown;
|
|
283
290
|
timeBucket?: unknown;
|
|
284
291
|
extractBucket?: unknown;
|
|
285
292
|
existsFilters?: unknown;
|
|
286
|
-
entityFilters?:
|
|
287
|
-
input: string;
|
|
288
|
-
resolver: string;
|
|
289
|
-
outputColumn: string;
|
|
290
|
-
}[] | undefined;
|
|
293
|
+
entityFilters?: unknown;
|
|
291
294
|
dateRange?: unknown;
|
|
292
|
-
orderBy?:
|
|
293
|
-
field: string;
|
|
294
|
-
dir?: "desc" | "asc" | undefined;
|
|
295
|
-
}[] | undefined;
|
|
295
|
+
orderBy?: unknown;
|
|
296
296
|
limit?: unknown;
|
|
297
297
|
answerMode?: unknown;
|
|
298
298
|
compareMode?: unknown;
|
package/dist/schemas.js
CHANGED
|
@@ -5,10 +5,10 @@ export const FilterSchema = z.object({
|
|
|
5
5
|
op: z.enum(["=", "!=", ">", "<", ">=", "<=", "in", "not_in", "like", "ilike", "is_null", "is_not_null"]),
|
|
6
6
|
value: z.union([
|
|
7
7
|
z.string(),
|
|
8
|
-
z.number(),
|
|
8
|
+
z.number().finite(),
|
|
9
9
|
z.boolean(),
|
|
10
|
-
z.array(z.union([z.string(), z.number()])),
|
|
11
|
-
]),
|
|
10
|
+
z.array(z.union([z.string(), z.number().finite()])),
|
|
11
|
+
]).optional(),
|
|
12
12
|
});
|
|
13
13
|
// ── Entity filter schema ──
|
|
14
14
|
export const EntityFilterSchema = z.object({
|
|
@@ -39,15 +39,15 @@ export const OrderBySchema = z.object({
|
|
|
39
39
|
// ── Smart query plan schema ──
|
|
40
40
|
export const SmartQueryPlanSchema = z.object({
|
|
41
41
|
dataset: z.string(),
|
|
42
|
-
select: z.array(z.string()).optional().default([]),
|
|
43
|
-
metrics: z.array(z.string()).optional().default([]),
|
|
44
|
-
groupBy: z.array(z.string()).optional().default([]),
|
|
42
|
+
select: z.preprocess((v) => (v === null ? [] : v), z.array(z.string()).optional().default([])),
|
|
43
|
+
metrics: z.preprocess((v) => (v === null ? [] : v), z.array(z.string()).optional().default([])),
|
|
44
|
+
groupBy: z.preprocess((v) => (v === null ? [] : v), z.array(z.string()).optional().default([])),
|
|
45
45
|
timeBucket: z.preprocess((v) => (v === null ? undefined : v), z.enum(["hour", "day", "week", "month", "quarter", "year"]).optional()),
|
|
46
46
|
extractBucket: z.preprocess((v) => (v === null ? undefined : v), z.enum(["hour_of_day", "day_of_week", "month_of_year", "quarter", "year", "week"]).optional()),
|
|
47
|
-
filters: z.array(FilterSchema).optional().default([]),
|
|
47
|
+
filters: z.preprocess((v) => (v === null ? [] : v), z.array(FilterSchema).optional().default([])),
|
|
48
48
|
having: z.preprocess((v) => (v === null ? [] : v), z.array(FilterSchema).optional().default([])),
|
|
49
49
|
existsFilters: z.preprocess((v) => (v === null ? [] : v), z.array(ExistsFilterSchema).optional().default([])),
|
|
50
|
-
entityFilters: z.array(EntityFilterSchema).optional().default([]),
|
|
50
|
+
entityFilters: z.preprocess((v) => (v === null ? [] : v), z.array(EntityFilterSchema).optional().default([])),
|
|
51
51
|
dateRange: z.preprocess((v) => {
|
|
52
52
|
if (v === null)
|
|
53
53
|
return undefined;
|
|
@@ -55,8 +55,8 @@ export const SmartQueryPlanSchema = z.object({
|
|
|
55
55
|
return undefined;
|
|
56
56
|
return v;
|
|
57
57
|
}, DateRangeSchema.optional()),
|
|
58
|
-
orderBy: z.array(OrderBySchema).optional().default([]),
|
|
59
|
-
limit: z.preprocess((v) => (v === null ? undefined : v), z.number().int().positive().max(200).optional()),
|
|
58
|
+
orderBy: z.preprocess((v) => (v === null ? [] : v), z.array(OrderBySchema).optional().default([])),
|
|
59
|
+
limit: z.preprocess((v) => (v === null ? undefined : v), z.number().finite().int().positive().max(200).optional()),
|
|
60
60
|
answerMode: z.preprocess((v) => (v === null ? undefined : v), z.enum(["rows", "aggregate_table", "scalar"]).optional().default("rows")),
|
|
61
61
|
compareMode: z.preprocess((v) => (v === null ? undefined : v), z.enum(['previous_period', 'year_over_year']).optional()),
|
|
62
62
|
});
|
package/dist/schemas.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,sBAAsB;AAEtB,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE;IACjB,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,aAAa,CAAC,CAAC;IACxG,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC;QACb,CAAC,CAAC,MAAM,EAAE;QACV,CAAC,CAAC,MAAM,EAAE;
|
|
1
|
+
{"version":3,"file":"schemas.js","sourceRoot":"","sources":["../src/schemas.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +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,EAAE,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,YAAY,GAAG,WAAW,
|
|
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,EAAE,OAAO,CAAC,EAAE,OAAO,YAAY,EAAE,YAAY,GAAG,WAAW,CA2YpI"}
|
package/dist/sql-builder.js
CHANGED
|
@@ -2,8 +2,8 @@ import { InferError } from './errors.js';
|
|
|
2
2
|
// Use pg driver's native escapeIdentifier pattern
|
|
3
3
|
// Since we can't import pg at compile time (it's optional), we implement the safe quoting
|
|
4
4
|
function quoteIdentifier(id) {
|
|
5
|
-
// Validate: identifiers must be alphanumeric
|
|
6
|
-
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(id)) {
|
|
5
|
+
// Validate: identifiers must be alphanumeric, underscore, or hyphen (no SQL injection)
|
|
6
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_-]*$/.test(id)) {
|
|
7
7
|
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid identifier: ${id}`);
|
|
8
8
|
}
|
|
9
9
|
return `"${id.replace(/"/g, '""')}"`;
|
|
@@ -45,16 +45,38 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
45
45
|
const nameCol = targetDs.selectableColumns.find(c => ['name', 'title', 'label', 'display_name', 'full_name', 'username', 'email'].includes(c));
|
|
46
46
|
if (!nameCol)
|
|
47
47
|
continue;
|
|
48
|
-
|
|
48
|
+
let alias = bareTable;
|
|
49
|
+
if (joinAliases.has(bareTable)) {
|
|
50
|
+
// Table already joined — use a unique alias to avoid conflicts (e.g. two FKs to same table)
|
|
51
|
+
alias = `${bareTable}__${col}`;
|
|
52
|
+
if (!joinAliases.has(alias)) {
|
|
53
|
+
const targetFull = `${quoteIdentifier(targetDs.table.schema)}.${quoteIdentifier(targetDs.table.name)}`;
|
|
54
|
+
joinAliases.set(alias, alias);
|
|
55
|
+
joinClauses.push(`LEFT JOIN ${targetFull} AS ${quoteIdentifier(alias)} ON ${quoteIdentifier(mainAlias)}.${quoteIdentifier(col)} = ${quoteIdentifier(alias)}.${quoteIdentifier(joinDef.foreignColumn)}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
49
59
|
const targetFull = `${quoteIdentifier(targetDs.table.schema)}.${quoteIdentifier(targetDs.table.name)}`;
|
|
50
60
|
joinAliases.set(bareTable, bareTable);
|
|
51
61
|
joinClauses.push(`LEFT JOIN ${targetFull} AS ${quoteIdentifier(bareTable)} ON ${quoteIdentifier(mainAlias)}.${quoteIdentifier(col)} = ${quoteIdentifier(bareTable)}.${quoteIdentifier(joinDef.foreignColumn)}`);
|
|
52
62
|
}
|
|
53
|
-
const fkRef = `${quoteIdentifier(
|
|
54
|
-
|
|
63
|
+
const fkRef = `${quoteIdentifier(alias)}.${quoteIdentifier(nameCol)}`;
|
|
64
|
+
// Use a unique alias that won't collide with existing columns
|
|
65
|
+
const fkAlias = col.replace(/_id$/, '') + '_name';
|
|
66
|
+
const safeFkAlias = plan.dataset.selectableColumns.includes(fkAlias) ? `${fkAlias}_fk` : fkAlias;
|
|
67
|
+
fkNameColumns.push(`${fkRef} AS ${quoteIdentifier(safeFkAlias)}`);
|
|
55
68
|
fkGroupByRefs.push(fkRef);
|
|
56
69
|
}
|
|
57
70
|
}
|
|
71
|
+
// ── Validate time/extract buckets at runtime ──
|
|
72
|
+
const VALID_TIME_BUCKETS = new Set(['hour', 'day', 'week', 'month', 'quarter', 'year']);
|
|
73
|
+
const VALID_EXTRACT_BUCKETS = new Set(['hour_of_day', 'day_of_week', 'month_of_year', 'quarter', 'year', 'week']);
|
|
74
|
+
if (plan.timeBucket && !VALID_TIME_BUCKETS.has(plan.timeBucket)) {
|
|
75
|
+
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid timeBucket: ${plan.timeBucket}`);
|
|
76
|
+
}
|
|
77
|
+
if (plan.extractBucket && !VALID_EXTRACT_BUCKETS.has(plan.extractBucket)) {
|
|
78
|
+
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid extractBucket: ${plan.extractBucket}`);
|
|
79
|
+
}
|
|
58
80
|
// ── Determine which groupBy columns need DATE_TRUNC or EXTRACT ──
|
|
59
81
|
const dateGroupColumns = new Set();
|
|
60
82
|
const extractGroupColumns = new Set();
|
|
@@ -83,7 +105,7 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
83
105
|
case 'quarter': return `EXTRACT(QUARTER FROM ${ref})`;
|
|
84
106
|
case 'year': return `EXTRACT(YEAR FROM ${ref})`;
|
|
85
107
|
case 'week': return `EXTRACT(WEEK FROM ${ref})`;
|
|
86
|
-
default:
|
|
108
|
+
default: throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid extractBucket: ${plan.extractBucket}`);
|
|
87
109
|
}
|
|
88
110
|
};
|
|
89
111
|
// Helper: resolve a column reference (supports dot-notation for joins)
|
|
@@ -147,11 +169,11 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
147
169
|
return expr;
|
|
148
170
|
// Replace bare column refs (not already qualified with table.) inside the expression
|
|
149
171
|
// Match word chars that aren't SQL keywords or already qualified
|
|
150
|
-
return expr.replace(/"([a-zA-Z_][a-zA-Z0-9_]*)"/g, (match, colName) => {
|
|
172
|
+
return expr.replace(/(?<!\.)("([a-zA-Z_][a-zA-Z0-9_]*)")(?!\.)/g, (match, quoted, colName) => {
|
|
151
173
|
// Check if this column exists on the main dataset
|
|
152
174
|
if (plan.dataset.selectableColumns.includes(colName) ||
|
|
153
175
|
plan.dataset.filterableColumns.includes(colName)) {
|
|
154
|
-
return `${quoteIdentifier(mainAlias)}.${
|
|
176
|
+
return `${quoteIdentifier(mainAlias)}.${quoted}`;
|
|
155
177
|
}
|
|
156
178
|
return match;
|
|
157
179
|
});
|
|
@@ -164,14 +186,14 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
164
186
|
selectCols = [...groupCols, ...metricCols];
|
|
165
187
|
}
|
|
166
188
|
else if (plan.select.length > 0) {
|
|
167
|
-
selectCols = plan.select.map(col =>
|
|
189
|
+
selectCols = plan.select.map(col => resolveColRef(col));
|
|
168
190
|
}
|
|
169
191
|
else if (plan.groupBy.length > 0 && plan.metrics.length === 0) {
|
|
170
192
|
// In rows mode with groupBy but no explicit select, only select grouped columns
|
|
171
|
-
selectCols = plan.groupBy.map(col =>
|
|
193
|
+
selectCols = plan.groupBy.map(col => resolveColRef(col));
|
|
172
194
|
}
|
|
173
195
|
else {
|
|
174
|
-
selectCols = plan.dataset.selectableColumns.map(col =>
|
|
196
|
+
selectCols = plan.dataset.selectableColumns.map(col => resolveColRef(col));
|
|
175
197
|
}
|
|
176
198
|
// Append FK name columns (auto-resolved human-readable names)
|
|
177
199
|
if (fkNameColumns.length > 0) {
|
|
@@ -182,19 +204,23 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
182
204
|
}
|
|
183
205
|
// ── Build WHERE clause ──
|
|
184
206
|
const whereParts = [];
|
|
185
|
-
// Tenant isolation (ALWAYS injected server-side)
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
207
|
+
// Tenant isolation (ALWAYS injected server-side when multi-tenant)
|
|
208
|
+
let tenantParamIdx = null;
|
|
209
|
+
if (ctx.tenantColumn !== undefined || (ctx.tenantId && ctx.tenantId.trim() !== '')) {
|
|
210
|
+
if (!ctx.tenantId || ctx.tenantId.trim() === '') {
|
|
211
|
+
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', 'Tenant ID is required but was not provided');
|
|
212
|
+
}
|
|
213
|
+
const tenantCol = ctx.tenantColumn ?? 'tenant_id';
|
|
214
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(tenantCol)) {
|
|
215
|
+
throw new InferError('SQL_VALIDATION_FAILED', 'compiler', `Invalid tenant column name: ${tenantCol}`);
|
|
216
|
+
}
|
|
217
|
+
const tenantRef = joinClauses.length > 0
|
|
218
|
+
? `${quoteIdentifier(mainAlias)}.${quoteIdentifier(tenantCol)}`
|
|
219
|
+
: quoteIdentifier(tenantCol);
|
|
220
|
+
params.push(ctx.tenantId);
|
|
221
|
+
tenantParamIdx = paramIdx;
|
|
222
|
+
whereParts.push(`${tenantRef} = $${paramIdx++}`);
|
|
192
223
|
}
|
|
193
|
-
const tenantRef = joinClauses.length > 0
|
|
194
|
-
? `${quoteIdentifier(mainAlias)}.${quoteIdentifier(tenantCol)}`
|
|
195
|
-
: quoteIdentifier(tenantCol);
|
|
196
|
-
params.push(ctx.tenantId);
|
|
197
|
-
whereParts.push(`${tenantRef} = $${paramIdx++}`);
|
|
198
224
|
// Location scoping (optional)
|
|
199
225
|
if (ctx.locationIds && ctx.locationIds.length > 0) {
|
|
200
226
|
const locationRef = joinClauses.length > 0
|
|
@@ -206,9 +232,9 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
206
232
|
// Date range
|
|
207
233
|
if (plan.dateRange && plan.dataset.dateColumn) {
|
|
208
234
|
params.push(plan.dateRange.startDate);
|
|
209
|
-
whereParts.push(`${
|
|
235
|
+
whereParts.push(`${resolveColRef(plan.dataset.dateColumn)} >= $${paramIdx++}`);
|
|
210
236
|
params.push(plan.dateRange.endDate);
|
|
211
|
-
whereParts.push(`${
|
|
237
|
+
whereParts.push(`${resolveColRef(plan.dataset.dateColumn)} <= $${paramIdx++}`);
|
|
212
238
|
}
|
|
213
239
|
// User filters (parameterized)
|
|
214
240
|
for (const filter of plan.filters) {
|
|
@@ -218,7 +244,7 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
218
244
|
col = buildExtractExpr(resolveColRef(filter.field));
|
|
219
245
|
}
|
|
220
246
|
else {
|
|
221
|
-
col =
|
|
247
|
+
col = resolveColRef(filter.field);
|
|
222
248
|
}
|
|
223
249
|
const op = sanitizeOperator(filter.op);
|
|
224
250
|
if (op === 'IS NULL') {
|
|
@@ -243,7 +269,7 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
243
269
|
// Entity filters (resolved IDs)
|
|
244
270
|
for (const entity of plan.resolvedEntities) {
|
|
245
271
|
if (entity.resolvedIds.length > 0) {
|
|
246
|
-
const col =
|
|
272
|
+
const col = resolveColRef(entity.outputColumn);
|
|
247
273
|
params.push(entity.resolvedIds);
|
|
248
274
|
whereParts.push(`${col} = ANY($${paramIdx++})`);
|
|
249
275
|
}
|
|
@@ -266,6 +292,11 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
266
292
|
subWhere += ` AND ${quoteIdentifier(subAlias)}.${quoteIdentifier(f.field)} ${op} $${paramIdx++}`;
|
|
267
293
|
}
|
|
268
294
|
}
|
|
295
|
+
// Add tenant isolation to EXISTS subquery
|
|
296
|
+
if (tenantParamIdx !== null) {
|
|
297
|
+
const tenantCol = ctx.tenantColumn ?? 'tenant_id';
|
|
298
|
+
subWhere += ` AND ${quoteIdentifier(subAlias)}.${quoteIdentifier(tenantCol)} = $${tenantParamIdx}`;
|
|
299
|
+
}
|
|
269
300
|
const existsOp = ef.mode === 'not_exists' ? 'NOT EXISTS' : 'EXISTS';
|
|
270
301
|
whereParts.push(`${existsOp} (SELECT 1 FROM ${targetTable} AS ${quoteIdentifier(subAlias)} WHERE ${subWhere})`);
|
|
271
302
|
}
|
|
@@ -288,6 +319,10 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
288
319
|
else if (op === 'IS NOT NULL') {
|
|
289
320
|
havingParts.push(`${fieldRef} IS NOT NULL`);
|
|
290
321
|
}
|
|
322
|
+
else if (op === 'IN' || op === 'NOT IN') {
|
|
323
|
+
params.push(filter.value);
|
|
324
|
+
havingParts.push(`${fieldRef} ${op === 'NOT IN' ? 'NOT ' : ''}= ANY($${paramIdx++})`);
|
|
325
|
+
}
|
|
291
326
|
else {
|
|
292
327
|
params.push(filter.value);
|
|
293
328
|
havingParts.push(`${fieldRef} ${op} $${paramIdx++}`);
|
|
@@ -298,14 +333,16 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
298
333
|
// ── Build ORDER BY (direction whitelisted to ASC/DESC only) ──
|
|
299
334
|
const orderBySql = plan.orderBy.length > 0
|
|
300
335
|
? `ORDER BY ${plan.orderBy.map(o => {
|
|
301
|
-
const ref =
|
|
302
|
-
?
|
|
303
|
-
:
|
|
304
|
-
|
|
336
|
+
const ref = extractGroupColumns.has(o.field) && plan.extractBucket
|
|
337
|
+
? buildExtractExpr(resolveColRef(o.field))
|
|
338
|
+
: dateGroupColumns.has(o.field) && plan.timeBucket
|
|
339
|
+
? formatGroupByRef(o.field)
|
|
340
|
+
: resolveColRef(o.field);
|
|
341
|
+
return `${ref} ${o.dir === 'desc' ? 'DESC NULLS LAST' : 'ASC'}`;
|
|
305
342
|
}).join(', ')}`
|
|
306
343
|
: '';
|
|
307
344
|
// ── LIMIT (always applied) ──
|
|
308
|
-
params.push(plan.limit);
|
|
345
|
+
params.push(Math.max(1, plan.limit));
|
|
309
346
|
const limitSql = `LIMIT $${paramIdx++}`;
|
|
310
347
|
// ── OFFSET (optional pagination) ──
|
|
311
348
|
let offsetSql = '';
|
|
@@ -331,8 +368,16 @@ export function compileSQL(plan, ctx, catalog) {
|
|
|
331
368
|
// compareMode: wrap in CTE with LAG window functions
|
|
332
369
|
if (plan.compareMode && plan.metrics.length > 0 && (plan.timeBucket || plan.extractBucket)) {
|
|
333
370
|
const metricAliases = plan.metrics.map(m => quoteIdentifier(m.id));
|
|
371
|
+
const timeCol = plan.groupBy.find(col => {
|
|
372
|
+
const t = plan.dataset.columnTypes[col];
|
|
373
|
+
return t === 'date' || t === 'timestamp';
|
|
374
|
+
}) || plan.groupBy[0] || 'period';
|
|
375
|
+
// Use the column alias as it appears in the inner query (after DATE_TRUNC/EXTRACT)
|
|
376
|
+
const timeAlias = colAlias(timeCol);
|
|
377
|
+
const partitionCols = plan.groupBy.filter(col => col !== timeCol).map(c => colAlias(c));
|
|
378
|
+
const partitionBy = partitionCols.length > 0 ? `PARTITION BY ${partitionCols.map(c => quoteIdentifier(c)).join(', ')} ` : '';
|
|
334
379
|
const lagCols = metricAliases.map(alias => {
|
|
335
|
-
return `${alias} - LAG(${alias}) OVER (ORDER BY ${quoteIdentifier(
|
|
380
|
+
return `${alias} - LAG(${alias}) OVER (${partitionBy}ORDER BY ${quoteIdentifier(timeAlias)}) AS ${alias.replace(/"$/, '_delta"')}`;
|
|
336
381
|
});
|
|
337
382
|
const wrappedSql = `WITH _base AS (\n${sql}\n)\nSELECT *, ${lagCols.join(', ')} FROM _base`;
|
|
338
383
|
return { sql: wrappedSql, params };
|