@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/src/index.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
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
|
+
|
|
9
|
+
// Core exports
|
|
10
|
+
import { QueryBuilder, IQueryBuilderOptions } from './query';
|
|
11
|
+
import { QueryParser, IParserOptions } from './parser';
|
|
12
|
+
import { SqlTranslator } from './translators/sql';
|
|
13
|
+
import { ISecurityOptions, QuerySecurityValidator } from './security';
|
|
14
|
+
import { IAdapter, IAdapterOptions } from './adapters';
|
|
15
|
+
|
|
16
|
+
export {
|
|
17
|
+
// Parser exports
|
|
18
|
+
QueryParser,
|
|
19
|
+
IParserOptions,
|
|
20
|
+
|
|
21
|
+
// Query builder exports
|
|
22
|
+
QueryBuilder,
|
|
23
|
+
IQueryBuilderOptions,
|
|
24
|
+
|
|
25
|
+
// Translator exports
|
|
26
|
+
SqlTranslator
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Re-export from modules
|
|
30
|
+
export * from './translators';
|
|
31
|
+
export * from './adapters';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create a new QueryBuilder instance
|
|
35
|
+
*/
|
|
36
|
+
export function createQueryBuilder<T>(
|
|
37
|
+
options?: IQueryBuilderOptions<T>
|
|
38
|
+
): QueryBuilder<T> {
|
|
39
|
+
return new QueryBuilder<T>(options);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Create a new QueryParser instance
|
|
44
|
+
*/
|
|
45
|
+
export function createQueryParser(options?: IParserOptions): QueryParser {
|
|
46
|
+
return new QueryParser(options);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Options for creating a new QueryKit instance
|
|
51
|
+
*/
|
|
52
|
+
export interface IQueryKitOptions<
|
|
53
|
+
TSchema extends Record<string, object> = Record<
|
|
54
|
+
string,
|
|
55
|
+
Record<string, unknown>
|
|
56
|
+
>
|
|
57
|
+
> {
|
|
58
|
+
/**
|
|
59
|
+
* The adapter to use for database connections
|
|
60
|
+
*/
|
|
61
|
+
adapter: IAdapter;
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* The schema to use for query validation
|
|
65
|
+
*/
|
|
66
|
+
schema: TSchema;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Security options for query validation
|
|
70
|
+
*/
|
|
71
|
+
security?: ISecurityOptions;
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Options to initialize the provided adapter
|
|
75
|
+
*/
|
|
76
|
+
adapterOptions?: IAdapterOptions & { [key: string]: unknown };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Define interfaces for return types
|
|
80
|
+
export interface IQueryExecutor<TResult> {
|
|
81
|
+
execute(): Promise<TResult[]>;
|
|
82
|
+
orderBy(field: string, direction?: 'asc' | 'desc'): IQueryExecutor<TResult>;
|
|
83
|
+
limit(count: number): IQueryExecutor<TResult>;
|
|
84
|
+
offset(count: number): IQueryExecutor<TResult>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface IWhereClause<TResult> {
|
|
88
|
+
where(queryString: string): IQueryExecutor<TResult>;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Public QueryKit type
|
|
93
|
+
*/
|
|
94
|
+
export type QueryKit<
|
|
95
|
+
TSchema extends Record<string, object>,
|
|
96
|
+
TRows extends { [K in keyof TSchema & string]: unknown } = {
|
|
97
|
+
[K in keyof TSchema & string]: unknown;
|
|
98
|
+
}
|
|
99
|
+
> = {
|
|
100
|
+
query<K extends keyof TSchema & string>(table: K): IWhereClause<TRows[K]>;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create a new QueryKit instance
|
|
105
|
+
*/
|
|
106
|
+
export function createQueryKit<
|
|
107
|
+
TSchema extends Record<string, object>,
|
|
108
|
+
TRows extends { [K in keyof TSchema & string]: unknown } = {
|
|
109
|
+
[K in keyof TSchema & string]: unknown;
|
|
110
|
+
}
|
|
111
|
+
>(options: IQueryKitOptions<TSchema>): QueryKit<TSchema, TRows> {
|
|
112
|
+
const parser = new QueryParser();
|
|
113
|
+
const securityValidator = new QuerySecurityValidator(options.security);
|
|
114
|
+
|
|
115
|
+
// Initialize adapter if options provided. If adapter is already initialized,
|
|
116
|
+
// calling initialize again with the same options should be a no-op for most adapters.
|
|
117
|
+
if (options.adapterOptions) {
|
|
118
|
+
const mergedAdapterOptions: IAdapterOptions & { [key: string]: unknown } = {
|
|
119
|
+
// Ensure adapter receives schema information if not already provided
|
|
120
|
+
schema: options.adapterOptions.schema ?? options.schema,
|
|
121
|
+
...options.adapterOptions
|
|
122
|
+
} as IAdapterOptions & { [key: string]: unknown };
|
|
123
|
+
|
|
124
|
+
try {
|
|
125
|
+
options.adapter.initialize(mergedAdapterOptions);
|
|
126
|
+
} catch {
|
|
127
|
+
// If initialization fails here, the adapter might already be initialized
|
|
128
|
+
// or require a different init path; we'll let execute-time errors surface.
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// This function would be expanded to include all QueryKit functionality
|
|
133
|
+
return {
|
|
134
|
+
query: <K extends keyof TSchema & string>(
|
|
135
|
+
table: K
|
|
136
|
+
): IWhereClause<TRows[K]> => {
|
|
137
|
+
return {
|
|
138
|
+
where: (queryString: string): IQueryExecutor<TRows[K]> => {
|
|
139
|
+
// Parse and validate the query
|
|
140
|
+
const expressionAst = parser.parse(queryString);
|
|
141
|
+
securityValidator.validate(
|
|
142
|
+
expressionAst,
|
|
143
|
+
options.schema as unknown as Record<string, Record<string, unknown>>
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
// Execution state accumulated via fluent calls
|
|
147
|
+
let orderByState: Record<string, 'asc' | 'desc'> = {};
|
|
148
|
+
let limitState: number | undefined;
|
|
149
|
+
let offsetState: number | undefined;
|
|
150
|
+
|
|
151
|
+
const executor: IQueryExecutor<TRows[K]> = {
|
|
152
|
+
orderBy: (
|
|
153
|
+
field: string,
|
|
154
|
+
direction: 'asc' | 'desc' = 'asc'
|
|
155
|
+
): IQueryExecutor<TRows[K]> => {
|
|
156
|
+
orderByState = { ...orderByState, [field]: direction };
|
|
157
|
+
return executor;
|
|
158
|
+
},
|
|
159
|
+
limit: (count: number): IQueryExecutor<TRows[K]> => {
|
|
160
|
+
limitState = count;
|
|
161
|
+
return executor;
|
|
162
|
+
},
|
|
163
|
+
offset: (count: number): IQueryExecutor<TRows[K]> => {
|
|
164
|
+
offsetState = count;
|
|
165
|
+
return executor;
|
|
166
|
+
},
|
|
167
|
+
execute: async (): Promise<TRows[K][]> => {
|
|
168
|
+
// Delegate to adapter
|
|
169
|
+
const results = await options.adapter.execute(
|
|
170
|
+
table,
|
|
171
|
+
expressionAst,
|
|
172
|
+
{
|
|
173
|
+
orderBy:
|
|
174
|
+
Object.keys(orderByState).length > 0
|
|
175
|
+
? orderByState
|
|
176
|
+
: undefined,
|
|
177
|
+
limit: limitState,
|
|
178
|
+
offset: offsetState
|
|
179
|
+
}
|
|
180
|
+
);
|
|
181
|
+
return results as TRows[K][];
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
|
|
185
|
+
return executor;
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Export all public APIs
|
|
193
|
+
export * from './parser';
|
|
194
|
+
export * from './security';
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { QueryParser } from './parser';
|
|
2
|
+
import { DrizzleTranslator } from './translators/drizzle';
|
|
3
|
+
import { DrizzleAdapter, IDrizzleDatabase } from './adapters/drizzle';
|
|
4
|
+
import { SQLWrapper, SQL, sql } from 'drizzle-orm';
|
|
5
|
+
import { createQueryBuilder, createQueryKit } from './index';
|
|
6
|
+
|
|
7
|
+
// Helper function to safely get SQL string value for testing
|
|
8
|
+
function getSqlString(sqlObj: SQL): string {
|
|
9
|
+
// For testing purposes only - extract a string representation
|
|
10
|
+
// of the SQL query that we can use in our assertions
|
|
11
|
+
try {
|
|
12
|
+
return JSON.stringify(sqlObj);
|
|
13
|
+
} catch (e) {
|
|
14
|
+
return String(sqlObj);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Define a user type for testing
|
|
19
|
+
interface ITodo {
|
|
20
|
+
id: number;
|
|
21
|
+
title: string;
|
|
22
|
+
priority: number;
|
|
23
|
+
status: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('QueryKit Integration Tests', () => {
|
|
27
|
+
// Set up mocks for the Drizzle adapter
|
|
28
|
+
const mockWhere = jest.fn().mockReturnThis();
|
|
29
|
+
const mockOrderBy = jest.fn().mockReturnThis();
|
|
30
|
+
const mockLimit = jest.fn().mockReturnThis();
|
|
31
|
+
const mockOffset = jest.fn().mockReturnThis();
|
|
32
|
+
const mockFrom = jest.fn().mockReturnValue({
|
|
33
|
+
where: mockWhere,
|
|
34
|
+
orderBy: mockOrderBy,
|
|
35
|
+
limit: mockLimit,
|
|
36
|
+
offset: mockOffset,
|
|
37
|
+
then: <T>(callback: (value: ITodo[]) => T) =>
|
|
38
|
+
Promise.resolve(
|
|
39
|
+
callback([
|
|
40
|
+
{ id: 1, title: 'Buy groceries', priority: 2, status: 'active' },
|
|
41
|
+
{ id: 2, title: 'Fix bug', priority: 3, status: 'active' }
|
|
42
|
+
])
|
|
43
|
+
)
|
|
44
|
+
});
|
|
45
|
+
const mockSelect = jest.fn().mockReturnValue({ from: mockFrom });
|
|
46
|
+
|
|
47
|
+
// Create a mock DB instance
|
|
48
|
+
const mockDb: IDrizzleDatabase = {
|
|
49
|
+
select: mockSelect
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Create mock schema with SQLWrapper fields
|
|
53
|
+
const mockSchema = {
|
|
54
|
+
todos: {
|
|
55
|
+
id: sql.raw('id') as unknown as SQLWrapper,
|
|
56
|
+
title: sql.raw('title') as unknown as SQLWrapper,
|
|
57
|
+
priority: sql.raw('priority') as unknown as SQLWrapper,
|
|
58
|
+
status: sql.raw('status') as unknown as SQLWrapper
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// Set up the adapter
|
|
63
|
+
const adapter = new DrizzleAdapter();
|
|
64
|
+
adapter.initialize({
|
|
65
|
+
db: mockDb,
|
|
66
|
+
schema: mockSchema
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
beforeEach(() => {
|
|
70
|
+
jest.clearAllMocks();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
describe('End-to-end Query', () => {
|
|
74
|
+
it('should parse, translate and execute a query', async () => {
|
|
75
|
+
// Parse a query expression
|
|
76
|
+
const parser = new QueryParser();
|
|
77
|
+
const expression = parser.parse('priority:>1 AND status:"active"');
|
|
78
|
+
|
|
79
|
+
// Execute the query using the adapter
|
|
80
|
+
const results = await adapter.execute<ITodo>('todos', expression);
|
|
81
|
+
|
|
82
|
+
// Verify results
|
|
83
|
+
expect(results).toHaveLength(2);
|
|
84
|
+
expect(results[0].title).toBe('Buy groceries');
|
|
85
|
+
expect(results[1].title).toBe('Fix bug');
|
|
86
|
+
|
|
87
|
+
// Verify that the mocks were called correctly
|
|
88
|
+
expect(mockSelect).toHaveBeenCalled();
|
|
89
|
+
expect(mockFrom).toHaveBeenCalled();
|
|
90
|
+
expect(mockWhere).toHaveBeenCalled();
|
|
91
|
+
|
|
92
|
+
// Verify the WHERE clause reflects the parsed query
|
|
93
|
+
const whereArg = mockWhere.mock.calls[0][0] as unknown as SQL;
|
|
94
|
+
const whereStr = getSqlString(whereArg);
|
|
95
|
+
expect(whereStr).toContain('priority');
|
|
96
|
+
expect(whereStr.toLowerCase()).toContain('active');
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should work with the query builder', async () => {
|
|
100
|
+
// Create a query builder
|
|
101
|
+
const queryBuilder = createQueryBuilder<ITodo>();
|
|
102
|
+
|
|
103
|
+
// Build a query
|
|
104
|
+
queryBuilder.where('priority', '>', 1).andWhere('status', '==', 'active');
|
|
105
|
+
|
|
106
|
+
// Get the expression from the builder
|
|
107
|
+
const expression = queryBuilder.getExpression();
|
|
108
|
+
|
|
109
|
+
// Execute the query using the adapter
|
|
110
|
+
const results = await adapter.execute<ITodo>('todos', expression);
|
|
111
|
+
|
|
112
|
+
// Verify results
|
|
113
|
+
expect(results).toHaveLength(2);
|
|
114
|
+
expect(results[0].id).toBe(1);
|
|
115
|
+
expect(results[1].id).toBe(2);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe('Translation Pipeline', () => {
|
|
120
|
+
it('should translate expressions consistently', () => {
|
|
121
|
+
// Parse a query string
|
|
122
|
+
const parser = new QueryParser();
|
|
123
|
+
const stringExpression = parser.parse('status:"active" OR priority:>2');
|
|
124
|
+
|
|
125
|
+
// Create the same expression with the builder
|
|
126
|
+
const queryBuilder = createQueryBuilder<ITodo>();
|
|
127
|
+
const builderExpression = queryBuilder
|
|
128
|
+
.where('status', '==', 'active')
|
|
129
|
+
.orWhere('priority', '>', 2)
|
|
130
|
+
.getExpression();
|
|
131
|
+
|
|
132
|
+
// Create a translator
|
|
133
|
+
const translator = new DrizzleTranslator();
|
|
134
|
+
|
|
135
|
+
// Translate both expressions
|
|
136
|
+
const stringTranslated = translator.translate(stringExpression);
|
|
137
|
+
const builderTranslated = translator.translate(builderExpression);
|
|
138
|
+
|
|
139
|
+
// The SQL from both translations should have the same structure
|
|
140
|
+
// (We can't directly compare SQL objects)
|
|
141
|
+
const stringTranslatedStr = getSqlString(stringTranslated);
|
|
142
|
+
const builderTranslatedStr = getSqlString(builderTranslated);
|
|
143
|
+
|
|
144
|
+
expect(stringTranslatedStr).toContain('OR');
|
|
145
|
+
expect(builderTranslatedStr).toContain('OR');
|
|
146
|
+
|
|
147
|
+
// Both translations should contain similar structural elements
|
|
148
|
+
// (Avoiding exact string matches as SQL objects might differ in representation)
|
|
149
|
+
if (
|
|
150
|
+
stringTranslatedStr.includes('status') &&
|
|
151
|
+
builderTranslatedStr.includes('status')
|
|
152
|
+
) {
|
|
153
|
+
expect(true).toBe(true); // Both contain 'status'
|
|
154
|
+
} else {
|
|
155
|
+
expect(stringTranslatedStr).toContain('status');
|
|
156
|
+
expect(builderTranslatedStr).toContain('status');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (
|
|
160
|
+
stringTranslatedStr.includes('priority') &&
|
|
161
|
+
builderTranslatedStr.includes('priority')
|
|
162
|
+
) {
|
|
163
|
+
expect(true).toBe(true); // Both contain 'priority'
|
|
164
|
+
} else {
|
|
165
|
+
expect(stringTranslatedStr).toContain('priority');
|
|
166
|
+
expect(builderTranslatedStr).toContain('priority');
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
describe('Fluent API execution', () => {
|
|
172
|
+
it('executes a fluent query via createQueryKit', async () => {
|
|
173
|
+
const adapter = new DrizzleAdapter();
|
|
174
|
+
adapter.initialize({ db: mockDb, schema: mockSchema });
|
|
175
|
+
|
|
176
|
+
const qk = createQueryKit({
|
|
177
|
+
adapter,
|
|
178
|
+
schema: { todos: { id: {}, title: {}, priority: {}, status: {} } }
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const results = await qk
|
|
182
|
+
.query('todos')
|
|
183
|
+
.where('priority:>1 AND status:"active"')
|
|
184
|
+
.orderBy('priority', 'desc')
|
|
185
|
+
.limit(10)
|
|
186
|
+
.execute();
|
|
187
|
+
|
|
188
|
+
expect(results).toHaveLength(2);
|
|
189
|
+
expect(mockSelect).toHaveBeenCalled();
|
|
190
|
+
expect(mockFrom).toHaveBeenCalled();
|
|
191
|
+
expect(mockWhere).toHaveBeenCalled();
|
|
192
|
+
expect(mockOrderBy).toHaveBeenCalled();
|
|
193
|
+
expect(mockLimit).toHaveBeenCalledWith(10);
|
|
194
|
+
|
|
195
|
+
// Verify WHERE clause from fluent path
|
|
196
|
+
const whereArg = mockWhere.mock.calls[0][0] as unknown as SQL;
|
|
197
|
+
const whereStr = getSqlString(whereArg);
|
|
198
|
+
expect(whereStr).toContain('priority');
|
|
199
|
+
expect(whereStr.toLowerCase()).toContain('active');
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
});
|