@gblikas/querykit 0.0.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.
- package/.cursor/BUGBOT.md +21 -0
- package/.cursor/rules/01-project-structure.mdc +77 -0
- package/.cursor/rules/02-typescript-standards.mdc +105 -0
- package/.cursor/rules/03-testing-standards.mdc +78 -0
- package/.cursor/rules/04-query-language.mdc +79 -0
- package/.cursor/rules/05-solid-principles.mdc +118 -0
- package/.cursor/rules/liqe-readme-docs.mdc +438 -0
- package/.devcontainer/devcontainer.json +25 -0
- package/.eslintignore +1 -0
- package/.eslintrc.js +39 -0
- package/.github/dependabot.yml +12 -0
- package/.github/workflows/ci.yml +114 -0
- package/.github/workflows/publish.yml +61 -0
- package/.husky/pre-commit +30 -0
- package/.prettierrc +10 -0
- package/CONTRIBUTING.md +187 -0
- package/LICENSE +674 -0
- package/README.md +237 -0
- package/dist/adapters/drizzle/index.d.ts +122 -0
- package/dist/adapters/drizzle/index.js +166 -0
- package/dist/adapters/index.d.ts +7 -0
- package/dist/adapters/index.js +25 -0
- package/dist/adapters/types.d.ts +60 -0
- package/dist/adapters/types.js +8 -0
- package/dist/index.d.ts +75 -0
- package/dist/index.js +118 -0
- package/dist/parser/index.d.ts +2 -0
- package/dist/parser/index.js +18 -0
- package/dist/parser/parser.d.ts +51 -0
- package/dist/parser/parser.js +201 -0
- package/dist/parser/types.d.ts +68 -0
- package/dist/parser/types.js +5 -0
- package/dist/query/builder.d.ts +61 -0
- package/dist/query/builder.js +188 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +18 -0
- package/dist/query/types.d.ts +79 -0
- package/dist/query/types.js +2 -0
- package/dist/security/index.d.ts +2 -0
- package/dist/security/index.js +18 -0
- package/dist/security/types.d.ts +181 -0
- package/dist/security/types.js +43 -0
- package/dist/security/validator.d.ts +191 -0
- package/dist/security/validator.js +344 -0
- package/dist/translators/drizzle/index.d.ts +73 -0
- package/dist/translators/drizzle/index.js +260 -0
- package/dist/translators/index.d.ts +8 -0
- package/dist/translators/index.js +27 -0
- package/dist/translators/sql/index.d.ts +108 -0
- package/dist/translators/sql/index.js +252 -0
- package/dist/translators/types.d.ts +39 -0
- package/dist/translators/types.js +8 -0
- package/examples/qk-next/README.md +35 -0
- package/examples/qk-next/app/favicon.ico +0 -0
- package/examples/qk-next/app/globals.css +122 -0
- package/examples/qk-next/app/layout.tsx +121 -0
- package/examples/qk-next/app/page.tsx +813 -0
- package/examples/qk-next/app/providers.tsx +80 -0
- package/examples/qk-next/components/aurora-background.tsx +12 -0
- package/examples/qk-next/components/github-stars.tsx +51 -0
- package/examples/qk-next/components/mode-toggle.tsx +27 -0
- package/examples/qk-next/components/reactbits/blocks/Backgrounds/Aurora/Aurora.tsx +217 -0
- package/examples/qk-next/components/reactbits/blocks/Backgrounds/LightRays/LightRays.tsx +474 -0
- package/examples/qk-next/components/theme-provider.tsx +11 -0
- package/examples/qk-next/components/ui/card.tsx +92 -0
- package/examples/qk-next/components/ui/command.tsx +184 -0
- package/examples/qk-next/components/ui/dialog.tsx +143 -0
- package/examples/qk-next/components/ui/drawer.tsx +135 -0
- package/examples/qk-next/components/ui/hover-card.tsx +44 -0
- package/examples/qk-next/components/ui/icons.tsx +148 -0
- package/examples/qk-next/components/ui/sonner.tsx +26 -0
- package/examples/qk-next/components/ui/table.tsx +117 -0
- package/examples/qk-next/components.json +21 -0
- package/examples/qk-next/eslint.config.mjs +21 -0
- package/examples/qk-next/jsrepo.json +13 -0
- package/examples/qk-next/lib/utils.ts +6 -0
- package/examples/qk-next/next.config.ts +8 -0
- package/examples/qk-next/package.json +48 -0
- package/examples/qk-next/pnpm-lock.yaml +5558 -0
- package/examples/qk-next/postcss.config.mjs +5 -0
- package/examples/qk-next/public/file.svg +1 -0
- package/examples/qk-next/public/globe.svg +1 -0
- package/examples/qk-next/public/next.svg +1 -0
- package/examples/qk-next/public/vercel.svg +1 -0
- package/examples/qk-next/public/window.svg +1 -0
- package/examples/qk-next/tsconfig.json +42 -0
- package/examples/qk-next/types/sonner.d.ts +3 -0
- package/jest.config.js +26 -0
- package/package.json +51 -0
- package/src/adapters/drizzle/drizzle-adapter.test.ts +115 -0
- package/src/adapters/drizzle/index.ts +299 -0
- package/src/adapters/index.ts +11 -0
- package/src/adapters/types.ts +72 -0
- package/src/index.ts +194 -0
- package/src/integration.test.ts +202 -0
- package/src/parser/index.ts +2 -0
- package/src/parser/parser.test.ts +1056 -0
- package/src/parser/parser.ts +268 -0
- package/src/parser/types.ts +97 -0
- package/src/query/builder.test.ts +272 -0
- package/src/query/builder.ts +274 -0
- package/src/query/index.ts +2 -0
- package/src/query/types.ts +107 -0
- package/src/security/index.ts +2 -0
- package/src/security/types.ts +210 -0
- package/src/security/validator.test.ts +459 -0
- package/src/security/validator.ts +395 -0
- package/src/security.test.ts +366 -0
- package/src/translators/drizzle/drizzle-translator.test.ts +128 -0
- package/src/translators/drizzle/index.test.ts +45 -0
- package/src/translators/drizzle/index.ts +346 -0
- package/src/translators/index.ts +14 -0
- package/src/translators/sql/index.test.ts +45 -0
- package/src/translators/sql/index.ts +331 -0
- package/src/translators/sql/sql-translator.test.ts +419 -0
- package/src/translators/types.ts +44 -0
- package/src/types/sonner.d.ts +3 -0
- package/tsconfig.json +34 -0
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { QueryParser } from '../parser';
|
|
2
|
+
import { QueryExpression } from '../parser/types';
|
|
3
|
+
import {
|
|
4
|
+
ComparisonOperator,
|
|
5
|
+
IQueryBuilder,
|
|
6
|
+
IQueryBuilderOptions,
|
|
7
|
+
QueryField,
|
|
8
|
+
QueryValue,
|
|
9
|
+
SortDirection
|
|
10
|
+
} from './types';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Implementation of the type-safe query builder
|
|
14
|
+
*/
|
|
15
|
+
export class QueryBuilder<T> implements IQueryBuilder<T> {
|
|
16
|
+
private parser: QueryParser;
|
|
17
|
+
private expression: string = '';
|
|
18
|
+
private orderByClause: string = '';
|
|
19
|
+
private limitClause: string = '';
|
|
20
|
+
private offsetClause: string = '';
|
|
21
|
+
|
|
22
|
+
constructor(options: IQueryBuilderOptions<T> = {}) {
|
|
23
|
+
this.parser = new QueryParser({
|
|
24
|
+
caseInsensitiveFields: options.caseInsensitiveFields,
|
|
25
|
+
fieldMappings: options.fieldMappings as Record<string, string>
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Add a where clause to the query
|
|
31
|
+
*/
|
|
32
|
+
public where(queryString: string): IQueryBuilder<T>;
|
|
33
|
+
public where(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
34
|
+
public where(
|
|
35
|
+
fieldOrQueryString: QueryField<T> | string,
|
|
36
|
+
operator?: ComparisonOperator,
|
|
37
|
+
value?: QueryValue
|
|
38
|
+
): IQueryBuilder<T> {
|
|
39
|
+
if (operator === undefined || value === undefined) {
|
|
40
|
+
// Handle direct query string format
|
|
41
|
+
this.expression = fieldOrQueryString as string;
|
|
42
|
+
} else {
|
|
43
|
+
// Handle field, operator, value format
|
|
44
|
+
this.expression = this.buildComparison(
|
|
45
|
+
fieldOrQueryString as QueryField<T>,
|
|
46
|
+
operator,
|
|
47
|
+
value
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
return this;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Add an AND where clause to the query
|
|
55
|
+
*/
|
|
56
|
+
public andWhere(queryString: string): IQueryBuilder<T>;
|
|
57
|
+
public andWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
58
|
+
public andWhere(
|
|
59
|
+
fieldOrQueryString: QueryField<T> | string,
|
|
60
|
+
operator?: ComparisonOperator,
|
|
61
|
+
value?: QueryValue
|
|
62
|
+
): IQueryBuilder<T> {
|
|
63
|
+
if (!this.expression) {
|
|
64
|
+
if (typeof fieldOrQueryString === 'string' && (operator === undefined || value === undefined)) {
|
|
65
|
+
// Handle direct query string
|
|
66
|
+
return this.where(fieldOrQueryString);
|
|
67
|
+
} else {
|
|
68
|
+
// Handle field, operator, value format
|
|
69
|
+
return this.where(
|
|
70
|
+
fieldOrQueryString as QueryField<T>,
|
|
71
|
+
operator as ComparisonOperator,
|
|
72
|
+
value as QueryValue
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (operator === undefined || value === undefined) {
|
|
78
|
+
// Handle direct query string format
|
|
79
|
+
this.expression = `(${this.expression}) AND ${fieldOrQueryString}`;
|
|
80
|
+
} else {
|
|
81
|
+
// Handle field, operator, value format
|
|
82
|
+
this.expression = `(${this.expression}) AND ${this.buildComparison(
|
|
83
|
+
fieldOrQueryString as QueryField<T>,
|
|
84
|
+
operator,
|
|
85
|
+
value
|
|
86
|
+
)}`;
|
|
87
|
+
}
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Add an OR where clause to the query
|
|
93
|
+
*/
|
|
94
|
+
public orWhere(queryString: string): IQueryBuilder<T>;
|
|
95
|
+
public orWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
96
|
+
public orWhere(
|
|
97
|
+
fieldOrQueryString: QueryField<T> | string,
|
|
98
|
+
operator?: ComparisonOperator,
|
|
99
|
+
value?: QueryValue
|
|
100
|
+
): IQueryBuilder<T> {
|
|
101
|
+
if (!this.expression) {
|
|
102
|
+
if (typeof fieldOrQueryString === 'string' && (operator === undefined || value === undefined)) {
|
|
103
|
+
// Handle direct query string
|
|
104
|
+
return this.where(fieldOrQueryString);
|
|
105
|
+
} else {
|
|
106
|
+
// Handle field, operator, value format
|
|
107
|
+
return this.where(
|
|
108
|
+
fieldOrQueryString as QueryField<T>,
|
|
109
|
+
operator as ComparisonOperator,
|
|
110
|
+
value as QueryValue
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (operator === undefined || value === undefined) {
|
|
116
|
+
// Handle direct query string format
|
|
117
|
+
this.expression = `(${this.expression}) OR ${fieldOrQueryString}`;
|
|
118
|
+
} else {
|
|
119
|
+
// Handle field, operator, value format
|
|
120
|
+
this.expression = `(${this.expression}) OR ${this.buildComparison(
|
|
121
|
+
fieldOrQueryString as QueryField<T>,
|
|
122
|
+
operator,
|
|
123
|
+
value
|
|
124
|
+
)}`;
|
|
125
|
+
}
|
|
126
|
+
return this;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Add a NOT where clause to the query
|
|
131
|
+
*/
|
|
132
|
+
public notWhere(queryString: string): IQueryBuilder<T>;
|
|
133
|
+
public notWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
134
|
+
public notWhere(
|
|
135
|
+
fieldOrQueryString: QueryField<T> | string,
|
|
136
|
+
operator?: ComparisonOperator,
|
|
137
|
+
value?: QueryValue
|
|
138
|
+
): IQueryBuilder<T> {
|
|
139
|
+
if (!this.expression) {
|
|
140
|
+
if (operator === undefined || value === undefined) {
|
|
141
|
+
// Handle direct query string format
|
|
142
|
+
this.expression = `NOT ${fieldOrQueryString}`;
|
|
143
|
+
} else {
|
|
144
|
+
// Handle field, operator, value format
|
|
145
|
+
this.expression = `NOT ${this.buildComparison(
|
|
146
|
+
fieldOrQueryString as QueryField<T>,
|
|
147
|
+
operator,
|
|
148
|
+
value
|
|
149
|
+
)}`;
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
if (operator === undefined || value === undefined) {
|
|
153
|
+
// Handle direct query string format
|
|
154
|
+
this.expression = `(${this.expression}) AND NOT ${fieldOrQueryString}`;
|
|
155
|
+
} else {
|
|
156
|
+
// Handle field, operator, value format
|
|
157
|
+
this.expression = `(${this.expression}) AND NOT ${this.buildComparison(
|
|
158
|
+
fieldOrQueryString as QueryField<T>,
|
|
159
|
+
operator,
|
|
160
|
+
value
|
|
161
|
+
)}`;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Add an order by clause to the query
|
|
169
|
+
*/
|
|
170
|
+
public orderBy(field: QueryField<T>, direction: SortDirection = 'asc'): IQueryBuilder<T> {
|
|
171
|
+
this.orderByClause = `ORDER BY ${field} ${direction.toUpperCase()}`;
|
|
172
|
+
return this;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Add a limit to the query
|
|
177
|
+
*/
|
|
178
|
+
public limit(count: number): IQueryBuilder<T> {
|
|
179
|
+
this.limitClause = `LIMIT ${count}`;
|
|
180
|
+
return this;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Add an offset to the query
|
|
185
|
+
*/
|
|
186
|
+
public offset(count: number): IQueryBuilder<T> {
|
|
187
|
+
this.offsetClause = `OFFSET ${count}`;
|
|
188
|
+
return this;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Get the current query expression
|
|
193
|
+
*/
|
|
194
|
+
public getExpression(): QueryExpression {
|
|
195
|
+
return this.parser.parse(this.expression);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Get the current query as a string
|
|
200
|
+
*/
|
|
201
|
+
public toString(): string {
|
|
202
|
+
const clauses = [
|
|
203
|
+
this.expression,
|
|
204
|
+
this.orderByClause,
|
|
205
|
+
this.limitClause,
|
|
206
|
+
this.offsetClause
|
|
207
|
+
].filter(Boolean);
|
|
208
|
+
|
|
209
|
+
return clauses.join(' ');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Build a comparison expression
|
|
214
|
+
*/
|
|
215
|
+
private buildComparison(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): string {
|
|
216
|
+
// Map QueryKit operators to Liqe operators
|
|
217
|
+
const operatorMap: Record<ComparisonOperator, string> = {
|
|
218
|
+
'==': ':',
|
|
219
|
+
'!=': '!=',
|
|
220
|
+
'>': '>',
|
|
221
|
+
'>=': '>=',
|
|
222
|
+
'<': '<',
|
|
223
|
+
'<=': '<=',
|
|
224
|
+
'IN': 'in',
|
|
225
|
+
'NOT IN': 'not in',
|
|
226
|
+
'LIKE': ':'
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const liqeOperator = operatorMap[operator];
|
|
230
|
+
const formattedValue = this.formatValue(value, operator);
|
|
231
|
+
|
|
232
|
+
// For equality and LIKE operators, use field:value format (simple colon)
|
|
233
|
+
if (operator === '==' || operator === 'LIKE') {
|
|
234
|
+
return `${field}${liqeOperator}${formattedValue}`;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Based on Liqe docs, comparison operators are prefixed with colon
|
|
238
|
+
// e.g., 'height:>100', 'height:<100'
|
|
239
|
+
if (operator === '>' || operator === '>=' || operator === '<' || operator === '<=' || operator === '!=') {
|
|
240
|
+
return `${field}:${liqeOperator}${formattedValue}`;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// For array operators (IN, NOT IN), use the format field:operator[values]
|
|
244
|
+
if (operator === 'IN' || operator === 'NOT IN') {
|
|
245
|
+
return `${field}:${liqeOperator}${formattedValue}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// For other operators, use field:operator value format
|
|
249
|
+
return `${field}:${liqeOperator} ${formattedValue}`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Format a value for use in a query
|
|
254
|
+
*/
|
|
255
|
+
private formatValue(value: QueryValue, operator?: ComparisonOperator): string {
|
|
256
|
+
if (value === null) {
|
|
257
|
+
return 'null';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (Array.isArray(value)) {
|
|
261
|
+
return `[${value.map(v => this.formatValue(v)).join(',')}]`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (typeof value === 'string') {
|
|
265
|
+
// For LIKE operator with wildcard patterns, don't add quotes to allow pattern matching
|
|
266
|
+
if (operator === 'LIKE' && (value.includes('*') || value.includes('?'))) {
|
|
267
|
+
return value;
|
|
268
|
+
}
|
|
269
|
+
return `"${value}"`;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
return String(value);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { QueryExpression } from '../parser/types';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Represents a field in a query
|
|
5
|
+
*/
|
|
6
|
+
export type QueryField<T> = keyof T & string;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Represents a value that can be used in a query
|
|
10
|
+
*/
|
|
11
|
+
export type QueryValue = string | number | boolean | null | Array<string | number | boolean | null>;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents a comparison operator in a query
|
|
15
|
+
*/
|
|
16
|
+
export type ComparisonOperator =
|
|
17
|
+
| '=='
|
|
18
|
+
| '!='
|
|
19
|
+
| '>'
|
|
20
|
+
| '>='
|
|
21
|
+
| '<'
|
|
22
|
+
| '<='
|
|
23
|
+
| 'IN'
|
|
24
|
+
| 'NOT IN'
|
|
25
|
+
| 'LIKE';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Represents a logical operator in a query
|
|
29
|
+
*/
|
|
30
|
+
export type LogicalOperator =
|
|
31
|
+
| 'AND'
|
|
32
|
+
| 'OR'
|
|
33
|
+
| 'NOT';
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Represents a sort direction
|
|
37
|
+
*/
|
|
38
|
+
export type SortDirection = 'asc' | 'desc';
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Configuration options for the query builder
|
|
42
|
+
*/
|
|
43
|
+
export interface IQueryBuilderOptions<T> {
|
|
44
|
+
/**
|
|
45
|
+
* Whether to allow case-insensitive field names
|
|
46
|
+
*/
|
|
47
|
+
caseInsensitiveFields?: boolean;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Custom field name mappings
|
|
51
|
+
*/
|
|
52
|
+
fieldMappings?: Partial<Record<QueryField<T>, string>>;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Interface for a query builder
|
|
57
|
+
*/
|
|
58
|
+
export interface IQueryBuilder<T> {
|
|
59
|
+
/**
|
|
60
|
+
* Add a where clause to the query
|
|
61
|
+
*/
|
|
62
|
+
where(queryString: string): IQueryBuilder<T>;
|
|
63
|
+
where(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Add an AND where clause to the query
|
|
67
|
+
*/
|
|
68
|
+
andWhere(queryString: string): IQueryBuilder<T>;
|
|
69
|
+
andWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Add an OR where clause to the query
|
|
73
|
+
*/
|
|
74
|
+
orWhere(queryString: string): IQueryBuilder<T>;
|
|
75
|
+
orWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Add a NOT where clause to the query
|
|
79
|
+
*/
|
|
80
|
+
notWhere(queryString: string): IQueryBuilder<T>;
|
|
81
|
+
notWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Add an order by clause to the query
|
|
85
|
+
*/
|
|
86
|
+
orderBy(field: QueryField<T>, direction?: SortDirection): IQueryBuilder<T>;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Add a limit to the query
|
|
90
|
+
*/
|
|
91
|
+
limit(count: number): IQueryBuilder<T>;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Add an offset to the query
|
|
95
|
+
*/
|
|
96
|
+
offset(count: number): IQueryBuilder<T>;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Get the current query expression
|
|
100
|
+
*/
|
|
101
|
+
getExpression(): QueryExpression;
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get the current query as a string
|
|
105
|
+
*/
|
|
106
|
+
toString(): string;
|
|
107
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security configuration types for QueryKit
|
|
3
|
+
*
|
|
4
|
+
* This module defines the security configuration interface and default values
|
|
5
|
+
* used throughout QueryKit to enforce security boundaries and prevent abuse.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @interface ISecurityOptions
|
|
10
|
+
* @description Comprehensive security configuration options for QueryKit
|
|
11
|
+
*
|
|
12
|
+
* These options help protect your application from potential security issues,
|
|
13
|
+
* resource exhaustion, and performance problems when exposing QueryKit to users.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { createQueryKit, type ISecurityOptions } from 'querykit';
|
|
18
|
+
*
|
|
19
|
+
* // Configure security options
|
|
20
|
+
* const securityOptions: ISecurityOptions = {
|
|
21
|
+
* allowedFields: ['id', 'name', 'createdAt'],
|
|
22
|
+
* denyFields: ['password', 'secretKey'],
|
|
23
|
+
* maxQueryDepth: 5,
|
|
24
|
+
* maxClauseCount: 20
|
|
25
|
+
* };
|
|
26
|
+
*
|
|
27
|
+
* // Create QueryKit instance with security options
|
|
28
|
+
* const queryKit = createQueryKit({
|
|
29
|
+
* // ...other options
|
|
30
|
+
* security: securityOptions
|
|
31
|
+
* });
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export interface ISecurityOptions {
|
|
35
|
+
/**
|
|
36
|
+
* List of fields that are allowed to be queried.
|
|
37
|
+
* If empty, all fields in the schema are allowed by default.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* // Only allow specific fields to be queried
|
|
42
|
+
* allowedFields: ['id', 'name', 'email', 'createdAt']
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
allowedFields?: string[];
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* List of fields that are explicitly denied from being queried.
|
|
49
|
+
* These fields will be blocked even if they appear in allowedFields.
|
|
50
|
+
* Use this to protect sensitive data fields.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* ```typescript
|
|
54
|
+
* // Prevent querying of sensitive fields
|
|
55
|
+
* denyFields: ['password', 'secretToken', 'ssn']
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
denyFields?: string[];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Maximum nesting depth of query expressions.
|
|
62
|
+
* Prevents deeply nested queries that could impact performance.
|
|
63
|
+
*
|
|
64
|
+
* @default 10
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* // Allow only simple queries with limited nesting
|
|
69
|
+
* maxQueryDepth: 3
|
|
70
|
+
*
|
|
71
|
+
* // This would allow queries like:
|
|
72
|
+
* // title:"Meeting notes" && (priority > 2 || completed == true)
|
|
73
|
+
* // But would reject more deeply nested expressions
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
maxQueryDepth?: number;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Maximum number of clauses (AND/OR operations) in a query.
|
|
80
|
+
* Prevents overly complex queries that could impact performance.
|
|
81
|
+
*
|
|
82
|
+
* @default 50
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* // Limit query complexity
|
|
87
|
+
* maxClauseCount: 20
|
|
88
|
+
*
|
|
89
|
+
* // This would allow queries with up to 20 conditions joined by AND/OR
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
maxClauseCount?: number;
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Default limit for query results if none is specified by the client.
|
|
96
|
+
* Prevents unintentionally large result sets.
|
|
97
|
+
*
|
|
98
|
+
* @default 100
|
|
99
|
+
*
|
|
100
|
+
* @example
|
|
101
|
+
* ```typescript
|
|
102
|
+
* // Set conservative default limit
|
|
103
|
+
* defaultLimit: 50
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
defaultLimit?: number;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Maximum allowed limit for pagination.
|
|
110
|
+
* Prevents clients from requesting excessively large result sets.
|
|
111
|
+
*
|
|
112
|
+
* @default 1000
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
* ```typescript
|
|
116
|
+
* // Restrict maximum page size
|
|
117
|
+
* maxLimit: 500
|
|
118
|
+
*
|
|
119
|
+
* // Even if a client requests limit=10000, it will be capped at 500
|
|
120
|
+
* ```
|
|
121
|
+
*/
|
|
122
|
+
maxLimit?: number;
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Maximum string length for query values.
|
|
126
|
+
* Prevents memory exhaustion from extremely large string values.
|
|
127
|
+
*
|
|
128
|
+
* @default 1000
|
|
129
|
+
*
|
|
130
|
+
* @example
|
|
131
|
+
* ```typescript
|
|
132
|
+
* // Limit string length in query values
|
|
133
|
+
* maxValueLength: 500
|
|
134
|
+
*
|
|
135
|
+
* // Prevents attacks using extremely long strings in filters
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
maxValueLength?: number;
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Whether to sanitize wildcard patterns in LIKE queries to prevent regex DoS.
|
|
142
|
+
* When enabled, excessive wildcard patterns are sanitized or rejected.
|
|
143
|
+
*
|
|
144
|
+
* @default true
|
|
145
|
+
*
|
|
146
|
+
* @example
|
|
147
|
+
* ```typescript
|
|
148
|
+
* // Enable wildcard sanitization
|
|
149
|
+
* sanitizeWildcards: true
|
|
150
|
+
*
|
|
151
|
+
* // Prevents regex DoS attacks like: name LIKE "%a%a%a%a%a%a%a%a%..."
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
sanitizeWildcards?: boolean;
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Timeout in milliseconds for query execution.
|
|
158
|
+
* Prevents long-running queries from consuming excessive resources.
|
|
159
|
+
*
|
|
160
|
+
* @default 30000 (30 seconds)
|
|
161
|
+
*
|
|
162
|
+
* @example
|
|
163
|
+
* ```typescript
|
|
164
|
+
* // Set shorter timeout for API endpoints
|
|
165
|
+
* queryTimeout: 5000 // 5 seconds
|
|
166
|
+
*
|
|
167
|
+
* // Queries taking longer than 5 seconds will be terminated
|
|
168
|
+
* ```
|
|
169
|
+
*/
|
|
170
|
+
queryTimeout?: number;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Default security configuration values
|
|
175
|
+
*
|
|
176
|
+
* These defaults provide a reasonable balance between functionality and security.
|
|
177
|
+
* It's recommended to review and adjust these settings based on your specific use case.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* import { DEFAULT_SECURITY_OPTIONS } from 'querykit';
|
|
182
|
+
*
|
|
183
|
+
* // Use defaults but override specific options
|
|
184
|
+
* const securityOptions = {
|
|
185
|
+
* ...DEFAULT_SECURITY_OPTIONS,
|
|
186
|
+
* maxLimit: 500,
|
|
187
|
+
* queryTimeout: 10000
|
|
188
|
+
* };
|
|
189
|
+
* ```
|
|
190
|
+
*/
|
|
191
|
+
export const DEFAULT_SECURITY_OPTIONS: Required<ISecurityOptions> = {
|
|
192
|
+
// Field restrictions - by default, all schema fields are allowed
|
|
193
|
+
allowedFields: [], // Empty means "use schema fields"
|
|
194
|
+
denyFields: [], // Empty means no denied fields
|
|
195
|
+
|
|
196
|
+
// Query complexity limits
|
|
197
|
+
maxQueryDepth: 10, // Maximum nesting level of expressions
|
|
198
|
+
maxClauseCount: 50, // Maximum number of clauses (AND/OR operations)
|
|
199
|
+
|
|
200
|
+
// Resource protection
|
|
201
|
+
defaultLimit: 100, // Default result limit if none specified
|
|
202
|
+
maxLimit: 1000, // Maximum allowed limit for pagination
|
|
203
|
+
|
|
204
|
+
// Value sanitization
|
|
205
|
+
maxValueLength: 1000, // Maximum string length for query values
|
|
206
|
+
sanitizeWildcards: true, // Prevent regex DoS with wildcards in LIKE queries
|
|
207
|
+
|
|
208
|
+
// Performance safeguards
|
|
209
|
+
queryTimeout: 30000 // 30 second timeout by default
|
|
210
|
+
};
|