@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
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryKit - A comprehensive query toolkit for TypeScript
|
|
3
|
+
*
|
|
4
|
+
* QueryKit simplifies how you build and execute data queries across different
|
|
5
|
+
* environments with a unified, intuitive syntax for filtering, sorting, and
|
|
6
|
+
* transforming data.
|
|
7
|
+
*/
|
|
8
|
+
import { QueryBuilder, IQueryBuilderOptions } from './query';
|
|
9
|
+
import { QueryParser, IParserOptions } from './parser';
|
|
10
|
+
import { SqlTranslator } from './translators/sql';
|
|
11
|
+
import { ISecurityOptions } from './security';
|
|
12
|
+
import { IAdapter, IAdapterOptions } from './adapters';
|
|
13
|
+
export { QueryParser, IParserOptions, QueryBuilder, IQueryBuilderOptions, SqlTranslator };
|
|
14
|
+
export * from './translators';
|
|
15
|
+
export * from './adapters';
|
|
16
|
+
/**
|
|
17
|
+
* Create a new QueryBuilder instance
|
|
18
|
+
*/
|
|
19
|
+
export declare function createQueryBuilder<T>(options?: IQueryBuilderOptions<T>): QueryBuilder<T>;
|
|
20
|
+
/**
|
|
21
|
+
* Create a new QueryParser instance
|
|
22
|
+
*/
|
|
23
|
+
export declare function createQueryParser(options?: IParserOptions): QueryParser;
|
|
24
|
+
/**
|
|
25
|
+
* Options for creating a new QueryKit instance
|
|
26
|
+
*/
|
|
27
|
+
export interface IQueryKitOptions<TSchema extends Record<string, object> = Record<string, Record<string, unknown>>> {
|
|
28
|
+
/**
|
|
29
|
+
* The adapter to use for database connections
|
|
30
|
+
*/
|
|
31
|
+
adapter: IAdapter;
|
|
32
|
+
/**
|
|
33
|
+
* The schema to use for query validation
|
|
34
|
+
*/
|
|
35
|
+
schema: TSchema;
|
|
36
|
+
/**
|
|
37
|
+
* Security options for query validation
|
|
38
|
+
*/
|
|
39
|
+
security?: ISecurityOptions;
|
|
40
|
+
/**
|
|
41
|
+
* Options to initialize the provided adapter
|
|
42
|
+
*/
|
|
43
|
+
adapterOptions?: IAdapterOptions & {
|
|
44
|
+
[key: string]: unknown;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export interface IQueryExecutor<TResult> {
|
|
48
|
+
execute(): Promise<TResult[]>;
|
|
49
|
+
orderBy(field: string, direction?: 'asc' | 'desc'): IQueryExecutor<TResult>;
|
|
50
|
+
limit(count: number): IQueryExecutor<TResult>;
|
|
51
|
+
offset(count: number): IQueryExecutor<TResult>;
|
|
52
|
+
}
|
|
53
|
+
export interface IWhereClause<TResult> {
|
|
54
|
+
where(queryString: string): IQueryExecutor<TResult>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Public QueryKit type
|
|
58
|
+
*/
|
|
59
|
+
export type QueryKit<TSchema extends Record<string, object>, TRows extends {
|
|
60
|
+
[K in keyof TSchema & string]: unknown;
|
|
61
|
+
} = {
|
|
62
|
+
[K in keyof TSchema & string]: unknown;
|
|
63
|
+
}> = {
|
|
64
|
+
query<K extends keyof TSchema & string>(table: K): IWhereClause<TRows[K]>;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Create a new QueryKit instance
|
|
68
|
+
*/
|
|
69
|
+
export declare function createQueryKit<TSchema extends Record<string, object>, TRows extends {
|
|
70
|
+
[K in keyof TSchema & string]: unknown;
|
|
71
|
+
} = {
|
|
72
|
+
[K in keyof TSchema & string]: unknown;
|
|
73
|
+
}>(options: IQueryKitOptions<TSchema>): QueryKit<TSchema, TRows>;
|
|
74
|
+
export * from './parser';
|
|
75
|
+
export * from './security';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* QueryKit - A comprehensive query toolkit for TypeScript
|
|
4
|
+
*
|
|
5
|
+
* QueryKit simplifies how you build and execute data queries across different
|
|
6
|
+
* environments with a unified, intuitive syntax for filtering, sorting, and
|
|
7
|
+
* transforming data.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
21
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
22
|
+
};
|
|
23
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
24
|
+
exports.SqlTranslator = exports.QueryBuilder = exports.QueryParser = void 0;
|
|
25
|
+
exports.createQueryBuilder = createQueryBuilder;
|
|
26
|
+
exports.createQueryParser = createQueryParser;
|
|
27
|
+
exports.createQueryKit = createQueryKit;
|
|
28
|
+
// Core exports
|
|
29
|
+
const query_1 = require("./query");
|
|
30
|
+
Object.defineProperty(exports, "QueryBuilder", { enumerable: true, get: function () { return query_1.QueryBuilder; } });
|
|
31
|
+
const parser_1 = require("./parser");
|
|
32
|
+
Object.defineProperty(exports, "QueryParser", { enumerable: true, get: function () { return parser_1.QueryParser; } });
|
|
33
|
+
const sql_1 = require("./translators/sql");
|
|
34
|
+
Object.defineProperty(exports, "SqlTranslator", { enumerable: true, get: function () { return sql_1.SqlTranslator; } });
|
|
35
|
+
const security_1 = require("./security");
|
|
36
|
+
// Re-export from modules
|
|
37
|
+
__exportStar(require("./translators"), exports);
|
|
38
|
+
__exportStar(require("./adapters"), exports);
|
|
39
|
+
/**
|
|
40
|
+
* Create a new QueryBuilder instance
|
|
41
|
+
*/
|
|
42
|
+
function createQueryBuilder(options) {
|
|
43
|
+
return new query_1.QueryBuilder(options);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a new QueryParser instance
|
|
47
|
+
*/
|
|
48
|
+
function createQueryParser(options) {
|
|
49
|
+
return new parser_1.QueryParser(options);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Create a new QueryKit instance
|
|
53
|
+
*/
|
|
54
|
+
function createQueryKit(options) {
|
|
55
|
+
const parser = new parser_1.QueryParser();
|
|
56
|
+
const securityValidator = new security_1.QuerySecurityValidator(options.security);
|
|
57
|
+
// Initialize adapter if options provided. If adapter is already initialized,
|
|
58
|
+
// calling initialize again with the same options should be a no-op for most adapters.
|
|
59
|
+
if (options.adapterOptions) {
|
|
60
|
+
const mergedAdapterOptions = {
|
|
61
|
+
// Ensure adapter receives schema information if not already provided
|
|
62
|
+
schema: options.adapterOptions.schema ?? options.schema,
|
|
63
|
+
...options.adapterOptions
|
|
64
|
+
};
|
|
65
|
+
try {
|
|
66
|
+
options.adapter.initialize(mergedAdapterOptions);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// If initialization fails here, the adapter might already be initialized
|
|
70
|
+
// or require a different init path; we'll let execute-time errors surface.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// This function would be expanded to include all QueryKit functionality
|
|
74
|
+
return {
|
|
75
|
+
query: (table) => {
|
|
76
|
+
return {
|
|
77
|
+
where: (queryString) => {
|
|
78
|
+
// Parse and validate the query
|
|
79
|
+
const expressionAst = parser.parse(queryString);
|
|
80
|
+
securityValidator.validate(expressionAst, options.schema);
|
|
81
|
+
// Execution state accumulated via fluent calls
|
|
82
|
+
let orderByState = {};
|
|
83
|
+
let limitState;
|
|
84
|
+
let offsetState;
|
|
85
|
+
const executor = {
|
|
86
|
+
orderBy: (field, direction = 'asc') => {
|
|
87
|
+
orderByState = { ...orderByState, [field]: direction };
|
|
88
|
+
return executor;
|
|
89
|
+
},
|
|
90
|
+
limit: (count) => {
|
|
91
|
+
limitState = count;
|
|
92
|
+
return executor;
|
|
93
|
+
},
|
|
94
|
+
offset: (count) => {
|
|
95
|
+
offsetState = count;
|
|
96
|
+
return executor;
|
|
97
|
+
},
|
|
98
|
+
execute: async () => {
|
|
99
|
+
// Delegate to adapter
|
|
100
|
+
const results = await options.adapter.execute(table, expressionAst, {
|
|
101
|
+
orderBy: Object.keys(orderByState).length > 0
|
|
102
|
+
? orderByState
|
|
103
|
+
: undefined,
|
|
104
|
+
limit: limitState,
|
|
105
|
+
offset: offsetState
|
|
106
|
+
});
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
return executor;
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Export all public APIs
|
|
117
|
+
__exportStar(require("./parser"), exports);
|
|
118
|
+
__exportStar(require("./security"), exports);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./types"), exports);
|
|
18
|
+
__exportStar(require("./parser"), exports);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { IParserOptions, IQueryParser, QueryExpression } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Error thrown when query parsing fails
|
|
4
|
+
*/
|
|
5
|
+
export declare class QueryParseError extends Error {
|
|
6
|
+
constructor(message: string);
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Implementation of the QueryKit parser using Liqe
|
|
10
|
+
*/
|
|
11
|
+
export declare class QueryParser implements IQueryParser {
|
|
12
|
+
private options;
|
|
13
|
+
constructor(options?: IParserOptions);
|
|
14
|
+
/**
|
|
15
|
+
* Parse a query string into a QueryKit AST
|
|
16
|
+
*/
|
|
17
|
+
parse(query: string): QueryExpression;
|
|
18
|
+
/**
|
|
19
|
+
* Validate a query string
|
|
20
|
+
*/
|
|
21
|
+
validate(query: string): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Convert a Liqe AST node to a QueryKit expression
|
|
24
|
+
*/
|
|
25
|
+
private convertLiqeAst;
|
|
26
|
+
/**
|
|
27
|
+
* Convert a Liqe logical operator to a QueryKit operator
|
|
28
|
+
*/
|
|
29
|
+
private convertLogicalOperator;
|
|
30
|
+
/**
|
|
31
|
+
* Create a logical expression from Liqe nodes
|
|
32
|
+
*/
|
|
33
|
+
private createLogicalExpression;
|
|
34
|
+
/**
|
|
35
|
+
* Create a comparison expression
|
|
36
|
+
*/
|
|
37
|
+
private createComparisonExpression;
|
|
38
|
+
/**
|
|
39
|
+
* Convert a Liqe operator to a QueryKit operator
|
|
40
|
+
*/
|
|
41
|
+
private convertLiqeOperator;
|
|
42
|
+
/**
|
|
43
|
+
* Convert a Liqe value to a QueryKit value
|
|
44
|
+
* Security: Strict type checking to prevent NoSQL injection via objects
|
|
45
|
+
*/
|
|
46
|
+
private convertLiqeValue;
|
|
47
|
+
/**
|
|
48
|
+
* Normalize a field name based on parser options
|
|
49
|
+
*/
|
|
50
|
+
private normalizeFieldName;
|
|
51
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueryParser = exports.QueryParseError = void 0;
|
|
4
|
+
const liqe_1 = require("liqe");
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when query parsing fails
|
|
7
|
+
*/
|
|
8
|
+
class QueryParseError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = 'QueryParseError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
exports.QueryParseError = QueryParseError;
|
|
15
|
+
/**
|
|
16
|
+
* Implementation of the QueryKit parser using Liqe
|
|
17
|
+
*/
|
|
18
|
+
class QueryParser {
|
|
19
|
+
constructor(options = {}) {
|
|
20
|
+
this.options = {
|
|
21
|
+
caseInsensitiveFields: options.caseInsensitiveFields ?? false,
|
|
22
|
+
fieldMappings: options.fieldMappings ?? {}
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse a query string into a QueryKit AST
|
|
27
|
+
*/
|
|
28
|
+
parse(query) {
|
|
29
|
+
try {
|
|
30
|
+
const liqeAst = (0, liqe_1.parse)(query);
|
|
31
|
+
return this.convertLiqeAst(liqeAst);
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
throw new QueryParseError(`Failed to parse query: ${error instanceof Error ? error.message : String(error)}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Validate a query string
|
|
39
|
+
*/
|
|
40
|
+
validate(query) {
|
|
41
|
+
try {
|
|
42
|
+
const ast = (0, liqe_1.parse)(query);
|
|
43
|
+
this.convertLiqeAst(ast);
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Convert a Liqe AST node to a QueryKit expression
|
|
52
|
+
*/
|
|
53
|
+
convertLiqeAst(node) {
|
|
54
|
+
if (!node || typeof node !== 'object') {
|
|
55
|
+
throw new QueryParseError('Invalid AST node');
|
|
56
|
+
}
|
|
57
|
+
switch (node.type) {
|
|
58
|
+
case 'LogicalExpression': {
|
|
59
|
+
const logicalNode = node;
|
|
60
|
+
const operator = logicalNode.operator.operator;
|
|
61
|
+
return this.createLogicalExpression(this.convertLogicalOperator(operator), logicalNode.left, logicalNode.right);
|
|
62
|
+
}
|
|
63
|
+
case 'UnaryOperator': {
|
|
64
|
+
const unaryNode = node;
|
|
65
|
+
return this.createLogicalExpression('NOT', unaryNode.operand);
|
|
66
|
+
}
|
|
67
|
+
case 'Tag': {
|
|
68
|
+
const tagNode = node;
|
|
69
|
+
const field = tagNode.field;
|
|
70
|
+
const expression = tagNode.expression;
|
|
71
|
+
if (!field || !expression) {
|
|
72
|
+
throw new QueryParseError('Invalid field or expression in Tag node');
|
|
73
|
+
}
|
|
74
|
+
const fieldName = this.normalizeFieldName(field.name);
|
|
75
|
+
const operator = this.convertLiqeOperator(tagNode.operator.operator);
|
|
76
|
+
const value = this.convertLiqeValue(expression.value);
|
|
77
|
+
// Check for wildcard patterns in string values
|
|
78
|
+
if (operator === '==' && typeof value === 'string' && (value.includes('*') || value.includes('?'))) {
|
|
79
|
+
return this.createComparisonExpression(fieldName, 'LIKE', value);
|
|
80
|
+
}
|
|
81
|
+
return this.createComparisonExpression(fieldName, operator, value);
|
|
82
|
+
}
|
|
83
|
+
case 'EmptyExpression':
|
|
84
|
+
if ('left' in node && node.left) {
|
|
85
|
+
return this.convertLiqeAst(node.left);
|
|
86
|
+
}
|
|
87
|
+
throw new QueryParseError('Invalid empty expression');
|
|
88
|
+
case 'ParenthesizedExpression': {
|
|
89
|
+
const parenNode = node;
|
|
90
|
+
if (parenNode.expression) {
|
|
91
|
+
return this.convertLiqeAst(parenNode.expression);
|
|
92
|
+
}
|
|
93
|
+
throw new QueryParseError('Invalid parenthesized expression');
|
|
94
|
+
}
|
|
95
|
+
default:
|
|
96
|
+
throw new QueryParseError(`Unsupported node type: ${node.type}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Convert a Liqe logical operator to a QueryKit operator
|
|
101
|
+
*/
|
|
102
|
+
convertLogicalOperator(operator) {
|
|
103
|
+
switch (operator.toLowerCase()) {
|
|
104
|
+
case 'and':
|
|
105
|
+
return 'AND';
|
|
106
|
+
case 'or':
|
|
107
|
+
return 'OR';
|
|
108
|
+
case 'not':
|
|
109
|
+
return 'NOT';
|
|
110
|
+
default:
|
|
111
|
+
throw new QueryParseError(`Unsupported logical operator: ${operator}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create a logical expression from Liqe nodes
|
|
116
|
+
*/
|
|
117
|
+
createLogicalExpression(operator, left, right) {
|
|
118
|
+
return {
|
|
119
|
+
type: 'logical',
|
|
120
|
+
operator,
|
|
121
|
+
left: this.convertLiqeAst(left),
|
|
122
|
+
...(right && { right: this.convertLiqeAst(right) })
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create a comparison expression
|
|
127
|
+
*/
|
|
128
|
+
createComparisonExpression(field, operator, value) {
|
|
129
|
+
return {
|
|
130
|
+
type: 'comparison',
|
|
131
|
+
field,
|
|
132
|
+
operator,
|
|
133
|
+
value
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Convert a Liqe operator to a QueryKit operator
|
|
138
|
+
*/
|
|
139
|
+
convertLiqeOperator(operator) {
|
|
140
|
+
// Handle the case where operator is part of the value for comparison operators
|
|
141
|
+
if (operator === ':') {
|
|
142
|
+
return '==';
|
|
143
|
+
}
|
|
144
|
+
// Check if the operator is prefixed with a colon
|
|
145
|
+
const actualOperator = operator.startsWith(':') ? operator.substring(1) : operator;
|
|
146
|
+
// Map Liqe operators to QueryKit operators
|
|
147
|
+
const operatorMap = {
|
|
148
|
+
'=': '==',
|
|
149
|
+
'!=': '!=',
|
|
150
|
+
'>': '>',
|
|
151
|
+
'>=': '>=',
|
|
152
|
+
'<': '<',
|
|
153
|
+
'<=': '<=',
|
|
154
|
+
'in': 'IN',
|
|
155
|
+
'not in': 'NOT IN'
|
|
156
|
+
};
|
|
157
|
+
const queryKitOperator = operatorMap[actualOperator.toLowerCase()];
|
|
158
|
+
if (!queryKitOperator) {
|
|
159
|
+
throw new QueryParseError(`Unsupported operator: ${operator}`);
|
|
160
|
+
}
|
|
161
|
+
return queryKitOperator;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Convert a Liqe value to a QueryKit value
|
|
165
|
+
* Security: Strict type checking to prevent NoSQL injection via objects
|
|
166
|
+
*/
|
|
167
|
+
convertLiqeValue(value) {
|
|
168
|
+
// Security fix: Strict type checking to prevent object injection
|
|
169
|
+
if (value === null) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
|
|
173
|
+
return value;
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(value)) {
|
|
176
|
+
// Security fix: Recursively validate array elements
|
|
177
|
+
const validatedArray = value.map(item => {
|
|
178
|
+
if (typeof item === 'object' && item !== null) {
|
|
179
|
+
throw new QueryParseError('Object values are not allowed in arrays');
|
|
180
|
+
}
|
|
181
|
+
return this.convertLiqeValue(item);
|
|
182
|
+
});
|
|
183
|
+
return validatedArray;
|
|
184
|
+
}
|
|
185
|
+
// Security fix: Reject all object types to prevent NoSQL injection
|
|
186
|
+
if (typeof value === 'object') {
|
|
187
|
+
throw new QueryParseError('Object values are not supported for security reasons');
|
|
188
|
+
}
|
|
189
|
+
throw new QueryParseError(`Unsupported value type: ${typeof value}`);
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Normalize a field name based on parser options
|
|
193
|
+
*/
|
|
194
|
+
normalizeFieldName(field) {
|
|
195
|
+
const normalizedField = this.options.caseInsensitiveFields
|
|
196
|
+
? field.toLowerCase()
|
|
197
|
+
: field;
|
|
198
|
+
return this.options.fieldMappings[normalizedField] ?? normalizedField;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
exports.QueryParser = QueryParser;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core AST types for QueryKit's parser
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Represents a comparison operator in a query expression
|
|
6
|
+
*/
|
|
7
|
+
export type ComparisonOperator = '==' | '!=' | '>' | '>=' | '<' | '<=' | 'IN' | 'NOT IN' | 'LIKE';
|
|
8
|
+
/**
|
|
9
|
+
* Represents a logical operator in a query expression
|
|
10
|
+
*/
|
|
11
|
+
export type LogicalOperator = 'AND' | 'OR' | 'NOT';
|
|
12
|
+
/**
|
|
13
|
+
* Represents a value that can be used in a query expression
|
|
14
|
+
*/
|
|
15
|
+
export type QueryValue = string | number | boolean | null | Array<string | number | boolean | null>;
|
|
16
|
+
/**
|
|
17
|
+
* Represents a comparison expression node in the AST
|
|
18
|
+
*/
|
|
19
|
+
export interface IComparisonExpression {
|
|
20
|
+
type: 'comparison';
|
|
21
|
+
field: string;
|
|
22
|
+
operator: ComparisonOperator;
|
|
23
|
+
value: QueryValue;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Represents a logical expression node in the AST
|
|
27
|
+
*/
|
|
28
|
+
export interface ILogicalExpression {
|
|
29
|
+
type: 'logical';
|
|
30
|
+
operator: LogicalOperator;
|
|
31
|
+
left: QueryExpression;
|
|
32
|
+
right?: QueryExpression;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Represents any valid query expression node
|
|
36
|
+
*/
|
|
37
|
+
export type QueryExpression = IComparisonExpression | ILogicalExpression;
|
|
38
|
+
/**
|
|
39
|
+
* Configuration options for the parser
|
|
40
|
+
*/
|
|
41
|
+
export interface IParserOptions {
|
|
42
|
+
/**
|
|
43
|
+
* Whether to allow case-insensitive field names
|
|
44
|
+
*/
|
|
45
|
+
caseInsensitiveFields?: boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Custom field name mappings
|
|
48
|
+
*/
|
|
49
|
+
fieldMappings?: Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Interface for the query parser
|
|
53
|
+
*/
|
|
54
|
+
export interface IQueryParser {
|
|
55
|
+
/**
|
|
56
|
+
* Parse a query string into an AST
|
|
57
|
+
* @param query The query string to parse
|
|
58
|
+
* @returns The parsed AST
|
|
59
|
+
* @throws {QueryParseError} If the query is invalid
|
|
60
|
+
*/
|
|
61
|
+
parse(query: string): QueryExpression;
|
|
62
|
+
/**
|
|
63
|
+
* Validate a query string without fully parsing it
|
|
64
|
+
* @param query The query string to validate
|
|
65
|
+
* @returns true if the query is valid, false otherwise
|
|
66
|
+
*/
|
|
67
|
+
validate(query: string): boolean;
|
|
68
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { QueryExpression } from '../parser/types';
|
|
2
|
+
import { ComparisonOperator, IQueryBuilder, IQueryBuilderOptions, QueryField, QueryValue, SortDirection } from './types';
|
|
3
|
+
/**
|
|
4
|
+
* Implementation of the type-safe query builder
|
|
5
|
+
*/
|
|
6
|
+
export declare class QueryBuilder<T> implements IQueryBuilder<T> {
|
|
7
|
+
private parser;
|
|
8
|
+
private expression;
|
|
9
|
+
private orderByClause;
|
|
10
|
+
private limitClause;
|
|
11
|
+
private offsetClause;
|
|
12
|
+
constructor(options?: IQueryBuilderOptions<T>);
|
|
13
|
+
/**
|
|
14
|
+
* Add a where clause to the query
|
|
15
|
+
*/
|
|
16
|
+
where(queryString: string): IQueryBuilder<T>;
|
|
17
|
+
where(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
18
|
+
/**
|
|
19
|
+
* Add an AND where clause to the query
|
|
20
|
+
*/
|
|
21
|
+
andWhere(queryString: string): IQueryBuilder<T>;
|
|
22
|
+
andWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
23
|
+
/**
|
|
24
|
+
* Add an OR where clause to the query
|
|
25
|
+
*/
|
|
26
|
+
orWhere(queryString: string): IQueryBuilder<T>;
|
|
27
|
+
orWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
28
|
+
/**
|
|
29
|
+
* Add a NOT where clause to the query
|
|
30
|
+
*/
|
|
31
|
+
notWhere(queryString: string): IQueryBuilder<T>;
|
|
32
|
+
notWhere(field: QueryField<T>, operator: ComparisonOperator, value: QueryValue): IQueryBuilder<T>;
|
|
33
|
+
/**
|
|
34
|
+
* Add an order by clause to the query
|
|
35
|
+
*/
|
|
36
|
+
orderBy(field: QueryField<T>, direction?: SortDirection): IQueryBuilder<T>;
|
|
37
|
+
/**
|
|
38
|
+
* Add a limit to the query
|
|
39
|
+
*/
|
|
40
|
+
limit(count: number): IQueryBuilder<T>;
|
|
41
|
+
/**
|
|
42
|
+
* Add an offset to the query
|
|
43
|
+
*/
|
|
44
|
+
offset(count: number): IQueryBuilder<T>;
|
|
45
|
+
/**
|
|
46
|
+
* Get the current query expression
|
|
47
|
+
*/
|
|
48
|
+
getExpression(): QueryExpression;
|
|
49
|
+
/**
|
|
50
|
+
* Get the current query as a string
|
|
51
|
+
*/
|
|
52
|
+
toString(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Build a comparison expression
|
|
55
|
+
*/
|
|
56
|
+
private buildComparison;
|
|
57
|
+
/**
|
|
58
|
+
* Format a value for use in a query
|
|
59
|
+
*/
|
|
60
|
+
private formatValue;
|
|
61
|
+
}
|