@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/README.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# QueryKit
|
|
2
|
+
|
|
3
|
+
QueryKit is a modern query toolkit for Lucene-style search, designed to give developers a head-start for dynamic search. QueryKit simplifies how you build and execute fielded searchs across different databases and ORMs. It provides a unified, intuitive SDK for filtering, sorting, and transforming data, and handles the heavy lifting of parsing and translating those queries to your data source.
|
|
4
|
+
|
|
5
|
+
## What Does QueryKit Do?
|
|
6
|
+
|
|
7
|
+
QueryKit allows developers to define queries in a high-level, readable format and then run those queries anywhere (in the browser, on the server, or via CLI). Instead of writing different query logic for each layer of your stack, you can use QueryKit's consistent API to:
|
|
8
|
+
|
|
9
|
+
- **Filter and sort data with a Lucene-like query language** – For example, `status:done AND in:todos`.
|
|
10
|
+
|
|
11
|
+
- **Translate high-level queries to concrete implementations** – QueryKit takes your schema definition and converts it into the appropriate form for the environment.
|
|
12
|
+
|
|
13
|
+
- **Unify front‑end and back‑end filtering logic** – The same QueryKit query can be run in a front-end app for client-side filtering or on a server for database queries. This ensures consistency in how data is filtered across your application.
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
Below are various examples of how to use QueryKit.
|
|
18
|
+
|
|
19
|
+
**Drizzle ORM**
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
|
|
23
|
+
// schema.ts
|
|
24
|
+
import { serial, text, pgTable } from 'drizzle-orm/pg-core';
|
|
25
|
+
import { type InferSelectModel, type InferInsertModel } from 'drizzle-orm'
|
|
26
|
+
|
|
27
|
+
const users = pgTable('users', {
|
|
28
|
+
id: serial('id').primaryKey(),
|
|
29
|
+
name: text('name').notNull(),
|
|
30
|
+
status: text('status').notNull().default('draft')
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
type SelectUser = InferSelectModel<typeof users>;
|
|
34
|
+
|
|
35
|
+
// example.ts
|
|
36
|
+
import { SelectUser } from './schema';
|
|
37
|
+
import { createQueryKit } from 'querykit';
|
|
38
|
+
import { drizzleAdapter } from 'querykit/adapters/drizzle';
|
|
39
|
+
|
|
40
|
+
// Create a QueryKit instance with Drizzle adapter
|
|
41
|
+
const qk = createQueryKit({
|
|
42
|
+
adapter: drizzleAdapter,
|
|
43
|
+
schema: { users },
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Build a query using the Lucene-like query syntax
|
|
47
|
+
const query = qk.query('users')
|
|
48
|
+
.where('status:done AND name:"John *"')
|
|
49
|
+
.orderBy('name', 'asc')
|
|
50
|
+
.limit(10);
|
|
51
|
+
|
|
52
|
+
// Execute the query against your database
|
|
53
|
+
const results = await query.execute();
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Query Syntax
|
|
57
|
+
|
|
58
|
+
QueryKit uses Liqe Query Language (LQL), which is a Lucene-like syntax for filtering data:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
# Basic field queries
|
|
62
|
+
status:active # Field equals value (case insensitive)
|
|
63
|
+
status:"Active" # Field equals value (case sensitive)
|
|
64
|
+
|
|
65
|
+
# Comparison operators
|
|
66
|
+
priority:>2 # Greater than
|
|
67
|
+
priority:>=2 # Greater than or equal
|
|
68
|
+
priority:<2 # Less than
|
|
69
|
+
priority:<=2 # Less than or equal
|
|
70
|
+
priority:=2 # Exact equals
|
|
71
|
+
|
|
72
|
+
# Ranges
|
|
73
|
+
dueDate:[2023-01-01 TO 2023-01-31] # Inclusive range
|
|
74
|
+
dueDate:{2023-01-01 TO 2023-01-31} # Exclusive range
|
|
75
|
+
|
|
76
|
+
# Pattern matching
|
|
77
|
+
name:/Todo.*/ # Regular expression
|
|
78
|
+
title:intro* # Wildcard matching
|
|
79
|
+
|
|
80
|
+
# Boolean operators
|
|
81
|
+
status:active AND priority:>2 # AND operator
|
|
82
|
+
status:active OR status:pending # OR operator
|
|
83
|
+
NOT expired:true # NOT operator
|
|
84
|
+
-expired:true # Alternative NOT syntax
|
|
85
|
+
|
|
86
|
+
# Grouping
|
|
87
|
+
(status:active OR status:pending) AND priority:<3
|
|
88
|
+
|
|
89
|
+
# Implicit AND (space between expressions)
|
|
90
|
+
status:active priority:>2
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Installation
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
npm install querykit
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Dependencies
|
|
100
|
+
|
|
101
|
+
QueryKit leverages several key dependencies to provide its functionality:
|
|
102
|
+
|
|
103
|
+
- [**Liqe**](https://github.com/gajus/liqe): A lightweight and performant Lucene-like parser, serializer, and search engine. QueryKit uses Liqe for parsing the query syntax into an abstract syntax tree (AST) that can then be translated to various target languages.
|
|
104
|
+
|
|
105
|
+
- [**Drizzle ORM**](https://github.com/drizzle-team/drizzle-orm): A TypeScript ORM that's used for SQL database interactions. QueryKit's Drizzle adapter translates queries into Drizzle ORM queries.
|
|
106
|
+
|
|
107
|
+
Additional dependencies will be added as the project evolves.
|
|
108
|
+
|
|
109
|
+
## Security Features
|
|
110
|
+
|
|
111
|
+
QueryKit provides configurable security guardrails to protect your database from potentially harmful queries while maintaining flexibility. These features can be configured during initialization:
|
|
112
|
+
|
|
113
|
+
> **IMPORTANT DISCLAIMER**: While QueryKit provides guardrails to help protect against common query-related vulnerabilities, it is not a comprehensive security solution. These features are provided as helpful tools, not guarantees. You are still responsible for implementing proper authentication, authorization, and other security measures in your application. QueryKit does not guarantee protection against all forms of database attacks or query exploits.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { createQueryKit } from 'querykit';
|
|
117
|
+
import { drizzleAdapter } from 'querykit/adapters/drizzle';
|
|
118
|
+
|
|
119
|
+
const qk = createQueryKit({
|
|
120
|
+
adapter: drizzleAdapter,
|
|
121
|
+
schema: { users },
|
|
122
|
+
security: {
|
|
123
|
+
// Field restrictions
|
|
124
|
+
allowedFields: ['name', 'email', 'priority', 'status'], // Only these fields can be queried
|
|
125
|
+
denyFields: ['password', 'secretKey'], // These fields can never be queried
|
|
126
|
+
|
|
127
|
+
// Query complexity limits
|
|
128
|
+
maxQueryDepth: 5, // Maximum nesting level of expressions
|
|
129
|
+
maxClauseCount: 20, // Maximum number of clauses (AND/OR operations)
|
|
130
|
+
|
|
131
|
+
// Resource protection
|
|
132
|
+
defaultLimit: 100, // Default result limit if none specified
|
|
133
|
+
maxLimit: 1000, // Maximum allowed limit for pagination
|
|
134
|
+
|
|
135
|
+
// Value sanitization
|
|
136
|
+
maxValueLength: 100, // Maximum string length for query values
|
|
137
|
+
sanitizeWildcards: true, // Prevent regex DoS with wildcards in LIKE queries
|
|
138
|
+
|
|
139
|
+
// Performance safeguards
|
|
140
|
+
queryTimeout: 5000, // Timeout in milliseconds for query execution
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
By default, QueryKit applies sensible security defaults even without explicit configuration:
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Default security configuration
|
|
149
|
+
const DEFAULT_SECURITY = {
|
|
150
|
+
// Field restrictions - by default, all schema fields are allowed
|
|
151
|
+
allowedFields: [], // Empty means "use schema fields"
|
|
152
|
+
denyFields: [], // Empty means no denied fields
|
|
153
|
+
|
|
154
|
+
// Query complexity limits
|
|
155
|
+
maxQueryDepth: 10, // Maximum nesting level of expressions
|
|
156
|
+
maxClauseCount: 50, // Maximum number of clauses (AND/OR operations)
|
|
157
|
+
|
|
158
|
+
// Resource protection
|
|
159
|
+
defaultLimit: 100, // Default result limit if none specified
|
|
160
|
+
maxLimit: 1000, // Maximum allowed limit for pagination
|
|
161
|
+
|
|
162
|
+
// Value sanitization
|
|
163
|
+
maxValueLength: 1000, // Maximum string length for query values
|
|
164
|
+
sanitizeWildcards: true, // Prevent regex DoS with wildcards in LIKE queries
|
|
165
|
+
|
|
166
|
+
// Performance safeguards
|
|
167
|
+
queryTimeout: 30000, // 30 second timeout by default
|
|
168
|
+
}
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Security configurations can be stored in a separate file and imported:
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// security-config.json
|
|
175
|
+
{
|
|
176
|
+
"allowedFields": ["name", "email", "priority", "status"],
|
|
177
|
+
"maxQueryDepth": 5,
|
|
178
|
+
"maxClauseCount": 20,
|
|
179
|
+
"defaultLimit": 100
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// In your app
|
|
183
|
+
import securityConfig from './security-config.json';
|
|
184
|
+
|
|
185
|
+
const qk = createQueryKit({
|
|
186
|
+
adapter: drizzleAdapter,
|
|
187
|
+
schema: { users },
|
|
188
|
+
security: securityConfig
|
|
189
|
+
});
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Additional Security Recommendations
|
|
193
|
+
|
|
194
|
+
When using QueryKit in production, consider these additional security practices:
|
|
195
|
+
|
|
196
|
+
1. **Implement Authentication and Authorization**: QueryKit doesn't handle auth - integrate with your existing auth system.
|
|
197
|
+
2. **Use Rate Limiting**: Limit the number of queries a user can make in a given time period.
|
|
198
|
+
3. **Audit Logging**: Log all queries for security monitoring and debugging.
|
|
199
|
+
4. **Field-Level Access Control**: Use dynamic allowedFields based on user roles/permissions.
|
|
200
|
+
5. **Separate Query Context**: Consider separate QueryKit instances with different security settings for different contexts (admin vs. user).
|
|
201
|
+
|
|
202
|
+
## Roadmap
|
|
203
|
+
|
|
204
|
+
### Core Parsing Engine and DSL
|
|
205
|
+
- [x] Implement Lucene-style query syntax parser using Liqe
|
|
206
|
+
- [x] Create type-safe query building API
|
|
207
|
+
- [x] Develop internal AST representation
|
|
208
|
+
- [x] Implement consistent syntax for logical operators (AND, OR, NOT)
|
|
209
|
+
- [x] Support standard comparison operators (==, !=, >, >=, <, <=)
|
|
210
|
+
|
|
211
|
+
### First Adapters
|
|
212
|
+
- [x] Drizzle ORM integration
|
|
213
|
+
- [x] Implement SQL translation layer
|
|
214
|
+
- [ ] In-memory JavaScript filtering
|
|
215
|
+
- [x] Query validation and error handling
|
|
216
|
+
- [x] Support for schema-aware queries
|
|
217
|
+
|
|
218
|
+
### Advanced Features
|
|
219
|
+
- [ ] CLI tools for testing and debugging
|
|
220
|
+
- [x] Performance optimizations for SQL generation
|
|
221
|
+
- [x] Support for complex nested expressions
|
|
222
|
+
- [ ] Custom function support
|
|
223
|
+
- [ ] Pagination helpers
|
|
224
|
+
|
|
225
|
+
### Ecosystem Expansion
|
|
226
|
+
- [ ] Frontend query builder components
|
|
227
|
+
- [ ] Additional ORM adapters
|
|
228
|
+
- [ ] Server middleware for Express/Fastify
|
|
229
|
+
- [ ] TypeScript SDK generation
|
|
230
|
+
|
|
231
|
+
## Contributing
|
|
232
|
+
|
|
233
|
+
See the [CONTRIBUTING.md](CONTRIBUTING.md) file for details on how to get started.
|
|
234
|
+
|
|
235
|
+
## License
|
|
236
|
+
|
|
237
|
+
This project is licensed under the GPL License - see the [LICENSE](LICENSE) file for details.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drizzle ORM Adapter for QueryKit
|
|
3
|
+
*
|
|
4
|
+
* This adapter connects QueryKit to Drizzle ORM for database queries.
|
|
5
|
+
*/
|
|
6
|
+
import { IAdapter, IAdapterOptions, IQueryExecutionOptions } from '../types';
|
|
7
|
+
import { QueryExpression } from '../../parser/types';
|
|
8
|
+
import { SQL } from 'drizzle-orm';
|
|
9
|
+
import { QueryKit } from '../../index';
|
|
10
|
+
/**
|
|
11
|
+
* Type for Drizzle ORM database instance
|
|
12
|
+
*/
|
|
13
|
+
export interface IDrizzleDatabase {
|
|
14
|
+
select: () => {
|
|
15
|
+
from: (table: unknown) => IDrizzleQueryBuilder;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Type for Drizzle query builder
|
|
20
|
+
*/
|
|
21
|
+
export interface IDrizzleQueryBuilder {
|
|
22
|
+
where: (condition: SQL) => IDrizzleQueryBuilder;
|
|
23
|
+
orderBy: (...clauses: SQL[]) => IDrizzleQueryBuilder;
|
|
24
|
+
limit: (limit: number) => IDrizzleQueryBuilder;
|
|
25
|
+
offset: (offset: number) => IDrizzleQueryBuilder;
|
|
26
|
+
[Symbol.toStringTag]: string;
|
|
27
|
+
then<TResult1 = unknown, TResult2 = never>(onfulfilled?: ((value: unknown[]) => TResult1 | PromiseLike<TResult1>) | null, onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null): Promise<TResult1 | TResult2>;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Options specific to the Drizzle adapter
|
|
31
|
+
*/
|
|
32
|
+
export interface IDrizzleAdapterOptions<TSchema extends Record<string, unknown> = Record<string, unknown>> extends IAdapterOptions {
|
|
33
|
+
/**
|
|
34
|
+
* The Drizzle ORM database instance
|
|
35
|
+
*/
|
|
36
|
+
db: IDrizzleDatabase | unknown;
|
|
37
|
+
/**
|
|
38
|
+
* Schema information with Drizzle table definitions
|
|
39
|
+
*/
|
|
40
|
+
schema: TSchema;
|
|
41
|
+
/**
|
|
42
|
+
* Whether to normalize field names (e.g., lowercase them)
|
|
43
|
+
*/
|
|
44
|
+
normalizeFieldNames?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Options for Drizzle query execution
|
|
48
|
+
*/
|
|
49
|
+
export interface IDrizzleQueryExecutionOptions extends IQueryExecutionOptions {
|
|
50
|
+
/**
|
|
51
|
+
* Sort fields in the format: { field: 'asc' | 'desc' }
|
|
52
|
+
*/
|
|
53
|
+
orderBy?: Record<string, 'asc' | 'desc'>;
|
|
54
|
+
/**
|
|
55
|
+
* Maximum number of records to return
|
|
56
|
+
*/
|
|
57
|
+
limit?: number;
|
|
58
|
+
/**
|
|
59
|
+
* Number of records to skip
|
|
60
|
+
*/
|
|
61
|
+
offset?: number;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Error thrown when adapter operations fail
|
|
65
|
+
*/
|
|
66
|
+
export declare class DrizzleAdapterError extends Error {
|
|
67
|
+
constructor(message: string);
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Adapter for Drizzle ORM
|
|
71
|
+
*/
|
|
72
|
+
export declare class DrizzleAdapter<TSchema extends Record<string, unknown> = Record<string, unknown>> implements IAdapter<IDrizzleAdapterOptions<TSchema>> {
|
|
73
|
+
private db;
|
|
74
|
+
private schema;
|
|
75
|
+
private translator;
|
|
76
|
+
private initialized;
|
|
77
|
+
/**
|
|
78
|
+
* Optionally initialize via constructor for convenience
|
|
79
|
+
*/
|
|
80
|
+
constructor(options?: IDrizzleAdapterOptions<TSchema>);
|
|
81
|
+
/**
|
|
82
|
+
* Initialize the adapter with options
|
|
83
|
+
*/
|
|
84
|
+
initialize(options: IDrizzleAdapterOptions<TSchema>): void;
|
|
85
|
+
/**
|
|
86
|
+
* Execute a QueryKit expression using Drizzle ORM
|
|
87
|
+
*/
|
|
88
|
+
execute<TResult = unknown>(tableName: string, expression: QueryExpression, options?: IDrizzleQueryExecutionOptions): Promise<TResult[]>;
|
|
89
|
+
/**
|
|
90
|
+
* Check if an expression can be executed by this adapter
|
|
91
|
+
*/
|
|
92
|
+
canExecute(expression: QueryExpression): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Get a table from the schema
|
|
95
|
+
*/
|
|
96
|
+
private getTable;
|
|
97
|
+
/**
|
|
98
|
+
* Get a field from the schema
|
|
99
|
+
*/
|
|
100
|
+
private getSchemaField;
|
|
101
|
+
/**
|
|
102
|
+
* Ensure the adapter is initialized
|
|
103
|
+
*/
|
|
104
|
+
private ensureInitialized;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Convenience factory to create a pre-initialized Drizzle adapter
|
|
108
|
+
*/
|
|
109
|
+
export declare function drizzleAdapter<TSchema extends Record<string, unknown>>(options: IDrizzleAdapterOptions<TSchema>): DrizzleAdapter<TSchema>;
|
|
110
|
+
export type RowTypeFromDrizzleTable<TTable> = TTable extends {
|
|
111
|
+
$inferSelect: infer R;
|
|
112
|
+
} ? R : unknown;
|
|
113
|
+
export type RowMapFromDrizzleSchema<TSchema extends Record<string, unknown>> = {
|
|
114
|
+
[K in keyof TSchema]: RowTypeFromDrizzleTable<TSchema[K]>;
|
|
115
|
+
};
|
|
116
|
+
export declare function createDrizzleQueryKit<TSchema extends Record<string, object>>(args: {
|
|
117
|
+
db: unknown;
|
|
118
|
+
schema: TSchema;
|
|
119
|
+
normalizeFieldNames?: boolean;
|
|
120
|
+
fieldMappings?: Record<string, string>;
|
|
121
|
+
security?: import('../../security').ISecurityOptions;
|
|
122
|
+
}): QueryKit<TSchema, RowMapFromDrizzleSchema<TSchema>>;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Drizzle ORM Adapter for QueryKit
|
|
4
|
+
*
|
|
5
|
+
* This adapter connects QueryKit to Drizzle ORM for database queries.
|
|
6
|
+
*/
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.DrizzleAdapter = exports.DrizzleAdapterError = void 0;
|
|
9
|
+
exports.drizzleAdapter = drizzleAdapter;
|
|
10
|
+
exports.createDrizzleQueryKit = createDrizzleQueryKit;
|
|
11
|
+
const drizzle_1 = require("../../translators/drizzle");
|
|
12
|
+
const drizzle_orm_1 = require("drizzle-orm");
|
|
13
|
+
const index_1 = require("../../index");
|
|
14
|
+
/**
|
|
15
|
+
* Error thrown when adapter operations fail
|
|
16
|
+
*/
|
|
17
|
+
class DrizzleAdapterError extends Error {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message);
|
|
20
|
+
this.name = 'DrizzleAdapterError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
exports.DrizzleAdapterError = DrizzleAdapterError;
|
|
24
|
+
/**
|
|
25
|
+
* Adapter for Drizzle ORM
|
|
26
|
+
*/
|
|
27
|
+
class DrizzleAdapter {
|
|
28
|
+
/**
|
|
29
|
+
* Optionally initialize via constructor for convenience
|
|
30
|
+
*/
|
|
31
|
+
constructor(options) {
|
|
32
|
+
this.initialized = false;
|
|
33
|
+
if (options) {
|
|
34
|
+
this.initialize(options);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Initialize the adapter with options
|
|
39
|
+
*/
|
|
40
|
+
initialize(options) {
|
|
41
|
+
if (!options.db) {
|
|
42
|
+
throw new DrizzleAdapterError('Drizzle db instance is required');
|
|
43
|
+
}
|
|
44
|
+
if (!options.schema) {
|
|
45
|
+
throw new DrizzleAdapterError('Schema definition is required');
|
|
46
|
+
}
|
|
47
|
+
this.db = options.db;
|
|
48
|
+
this.schema = options.schema;
|
|
49
|
+
this.translator = new drizzle_1.DrizzleTranslator({
|
|
50
|
+
normalizeFieldNames: options.normalizeFieldNames,
|
|
51
|
+
fieldMappings: options.fieldMappings,
|
|
52
|
+
schema: options.schema
|
|
53
|
+
});
|
|
54
|
+
this.initialized = true;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Execute a QueryKit expression using Drizzle ORM
|
|
58
|
+
*/
|
|
59
|
+
async execute(tableName, expression, options) {
|
|
60
|
+
this.ensureInitialized();
|
|
61
|
+
const table = this.getTable(tableName);
|
|
62
|
+
if (!table) {
|
|
63
|
+
throw new DrizzleAdapterError(`Table ${tableName} not found in schema`);
|
|
64
|
+
}
|
|
65
|
+
try {
|
|
66
|
+
// Start with a base query
|
|
67
|
+
let query = this.db.select().from(table);
|
|
68
|
+
// Add where condition if expression is provided
|
|
69
|
+
if (expression) {
|
|
70
|
+
const condition = this.translator.translate(expression);
|
|
71
|
+
query = query.where(condition);
|
|
72
|
+
}
|
|
73
|
+
// Add ordering if specified
|
|
74
|
+
if (options?.orderBy) {
|
|
75
|
+
const orderClauses = [];
|
|
76
|
+
Object.entries(options.orderBy).forEach(([field, direction]) => {
|
|
77
|
+
// Try to find the field in the schema
|
|
78
|
+
const schemaField = this.getSchemaField(tableName, field);
|
|
79
|
+
if (schemaField) {
|
|
80
|
+
// If field exists in schema, use it directly
|
|
81
|
+
orderClauses.push(direction === 'asc' ? (0, drizzle_orm_1.asc)(schemaField) : (0, drizzle_orm_1.desc)(schemaField));
|
|
82
|
+
}
|
|
83
|
+
else {
|
|
84
|
+
// Otherwise use raw SQL
|
|
85
|
+
orderClauses.push((0, drizzle_orm_1.sql) `${drizzle_orm_1.sql.identifier(field)} ${drizzle_orm_1.sql.raw(direction.toUpperCase())}`);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
if (orderClauses.length > 0) {
|
|
89
|
+
query = query.orderBy(...orderClauses);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Add limit if specified
|
|
93
|
+
if (options?.limit !== undefined) {
|
|
94
|
+
query = query.limit(options.limit);
|
|
95
|
+
}
|
|
96
|
+
// Add offset if specified
|
|
97
|
+
if (options?.offset !== undefined) {
|
|
98
|
+
query = query.offset(options.offset);
|
|
99
|
+
}
|
|
100
|
+
// Execute the query
|
|
101
|
+
const result = await query;
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
throw new DrizzleAdapterError(`Failed to execute query: ${error instanceof Error ? error.message : String(error)}`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if an expression can be executed by this adapter
|
|
110
|
+
*/
|
|
111
|
+
canExecute(expression) {
|
|
112
|
+
try {
|
|
113
|
+
this.ensureInitialized();
|
|
114
|
+
return this.translator.canTranslate(expression);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get a table from the schema
|
|
122
|
+
*/
|
|
123
|
+
getTable(tableName) {
|
|
124
|
+
return this.schema[tableName];
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Get a field from the schema
|
|
128
|
+
*/
|
|
129
|
+
getSchemaField(tableName, fieldName) {
|
|
130
|
+
const schemaAsColumns = this.schema;
|
|
131
|
+
const table = schemaAsColumns[tableName];
|
|
132
|
+
if (table && fieldName in table) {
|
|
133
|
+
return table[fieldName];
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Ensure the adapter is initialized
|
|
139
|
+
*/
|
|
140
|
+
ensureInitialized() {
|
|
141
|
+
if (!this.initialized) {
|
|
142
|
+
throw new DrizzleAdapterError('Adapter has not been initialized');
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
exports.DrizzleAdapter = DrizzleAdapter;
|
|
147
|
+
/**
|
|
148
|
+
* Convenience factory to create a pre-initialized Drizzle adapter
|
|
149
|
+
*/
|
|
150
|
+
function drizzleAdapter(options) {
|
|
151
|
+
return new DrizzleAdapter(options);
|
|
152
|
+
}
|
|
153
|
+
function createDrizzleQueryKit(args) {
|
|
154
|
+
const adapter = new DrizzleAdapter();
|
|
155
|
+
adapter.initialize({
|
|
156
|
+
db: args.db,
|
|
157
|
+
schema: args.schema,
|
|
158
|
+
normalizeFieldNames: args.normalizeFieldNames,
|
|
159
|
+
fieldMappings: args.fieldMappings
|
|
160
|
+
});
|
|
161
|
+
return (0, index_1.createQueryKit)({
|
|
162
|
+
adapter,
|
|
163
|
+
schema: args.schema,
|
|
164
|
+
security: args.security
|
|
165
|
+
});
|
|
166
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* QueryKit Adapters
|
|
4
|
+
*
|
|
5
|
+
* Exports all adapter implementations for different database systems.
|
|
6
|
+
*/
|
|
7
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
8
|
+
if (k2 === undefined) k2 = k;
|
|
9
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
10
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
11
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
12
|
+
}
|
|
13
|
+
Object.defineProperty(o, k2, desc);
|
|
14
|
+
}) : (function(o, m, k, k2) {
|
|
15
|
+
if (k2 === undefined) k2 = k;
|
|
16
|
+
o[k2] = m[k];
|
|
17
|
+
}));
|
|
18
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
19
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
20
|
+
};
|
|
21
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
|
+
// Export base types
|
|
23
|
+
__exportStar(require("./types"), exports);
|
|
24
|
+
// Export Drizzle adapter
|
|
25
|
+
__exportStar(require("./drizzle"), exports);
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryKit Adapter Types
|
|
3
|
+
*
|
|
4
|
+
* These are the core interfaces for adapters, which connect QueryKit to
|
|
5
|
+
* external systems or libraries like Drizzle ORM.
|
|
6
|
+
*/
|
|
7
|
+
import { QueryExpression } from '../parser/types';
|
|
8
|
+
/**
|
|
9
|
+
* Options for configuring an adapter
|
|
10
|
+
*/
|
|
11
|
+
export interface IAdapterOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Schema information for type safety and validation
|
|
14
|
+
*/
|
|
15
|
+
schema?: Record<string, unknown>;
|
|
16
|
+
/**
|
|
17
|
+
* Field mappings from QueryKit fields to target database fields
|
|
18
|
+
*/
|
|
19
|
+
fieldMappings?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Options for a query execution
|
|
23
|
+
*/
|
|
24
|
+
export interface IQueryExecutionOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Optional transaction object
|
|
27
|
+
*/
|
|
28
|
+
transaction?: unknown;
|
|
29
|
+
/**
|
|
30
|
+
* Additional parameters specific to the adapter
|
|
31
|
+
*/
|
|
32
|
+
[key: string]: unknown;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Interface for a query adapter
|
|
36
|
+
*/
|
|
37
|
+
export interface IAdapter<TOptions extends IAdapterOptions = IAdapterOptions> {
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the adapter with options
|
|
40
|
+
*
|
|
41
|
+
* @param options Adapter-specific options
|
|
42
|
+
*/
|
|
43
|
+
initialize(options: TOptions): void;
|
|
44
|
+
/**
|
|
45
|
+
* Execute a QueryKit expression and return results
|
|
46
|
+
*
|
|
47
|
+
* @param tableName The table/collection name to query
|
|
48
|
+
* @param expression The QueryKit expression to execute
|
|
49
|
+
* @param options Optional execution options
|
|
50
|
+
* @returns The query results
|
|
51
|
+
*/
|
|
52
|
+
execute<T = unknown>(tableName: string, expression: QueryExpression, options?: IQueryExecutionOptions): Promise<T[]>;
|
|
53
|
+
/**
|
|
54
|
+
* Check if an expression can be executed by this adapter
|
|
55
|
+
*
|
|
56
|
+
* @param expression The QueryKit expression to check
|
|
57
|
+
* @returns true if the expression can be executed, false otherwise
|
|
58
|
+
*/
|
|
59
|
+
canExecute(expression: QueryExpression): boolean;
|
|
60
|
+
}
|