@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.
Files changed (118) hide show
  1. package/.cursor/BUGBOT.md +21 -0
  2. package/.cursor/rules/01-project-structure.mdc +77 -0
  3. package/.cursor/rules/02-typescript-standards.mdc +105 -0
  4. package/.cursor/rules/03-testing-standards.mdc +78 -0
  5. package/.cursor/rules/04-query-language.mdc +79 -0
  6. package/.cursor/rules/05-solid-principles.mdc +118 -0
  7. package/.cursor/rules/liqe-readme-docs.mdc +438 -0
  8. package/.devcontainer/devcontainer.json +25 -0
  9. package/.eslintignore +1 -0
  10. package/.eslintrc.js +39 -0
  11. package/.github/dependabot.yml +12 -0
  12. package/.github/workflows/ci.yml +114 -0
  13. package/.github/workflows/publish.yml +61 -0
  14. package/.husky/pre-commit +30 -0
  15. package/.prettierrc +10 -0
  16. package/CONTRIBUTING.md +187 -0
  17. package/LICENSE +674 -0
  18. package/README.md +237 -0
  19. package/dist/adapters/drizzle/index.d.ts +122 -0
  20. package/dist/adapters/drizzle/index.js +166 -0
  21. package/dist/adapters/index.d.ts +7 -0
  22. package/dist/adapters/index.js +25 -0
  23. package/dist/adapters/types.d.ts +60 -0
  24. package/dist/adapters/types.js +8 -0
  25. package/dist/index.d.ts +75 -0
  26. package/dist/index.js +118 -0
  27. package/dist/parser/index.d.ts +2 -0
  28. package/dist/parser/index.js +18 -0
  29. package/dist/parser/parser.d.ts +51 -0
  30. package/dist/parser/parser.js +201 -0
  31. package/dist/parser/types.d.ts +68 -0
  32. package/dist/parser/types.js +5 -0
  33. package/dist/query/builder.d.ts +61 -0
  34. package/dist/query/builder.js +188 -0
  35. package/dist/query/index.d.ts +2 -0
  36. package/dist/query/index.js +18 -0
  37. package/dist/query/types.d.ts +79 -0
  38. package/dist/query/types.js +2 -0
  39. package/dist/security/index.d.ts +2 -0
  40. package/dist/security/index.js +18 -0
  41. package/dist/security/types.d.ts +181 -0
  42. package/dist/security/types.js +43 -0
  43. package/dist/security/validator.d.ts +191 -0
  44. package/dist/security/validator.js +344 -0
  45. package/dist/translators/drizzle/index.d.ts +73 -0
  46. package/dist/translators/drizzle/index.js +260 -0
  47. package/dist/translators/index.d.ts +8 -0
  48. package/dist/translators/index.js +27 -0
  49. package/dist/translators/sql/index.d.ts +108 -0
  50. package/dist/translators/sql/index.js +252 -0
  51. package/dist/translators/types.d.ts +39 -0
  52. package/dist/translators/types.js +8 -0
  53. package/examples/qk-next/README.md +35 -0
  54. package/examples/qk-next/app/favicon.ico +0 -0
  55. package/examples/qk-next/app/globals.css +122 -0
  56. package/examples/qk-next/app/layout.tsx +121 -0
  57. package/examples/qk-next/app/page.tsx +813 -0
  58. package/examples/qk-next/app/providers.tsx +80 -0
  59. package/examples/qk-next/components/aurora-background.tsx +12 -0
  60. package/examples/qk-next/components/github-stars.tsx +51 -0
  61. package/examples/qk-next/components/mode-toggle.tsx +27 -0
  62. package/examples/qk-next/components/reactbits/blocks/Backgrounds/Aurora/Aurora.tsx +217 -0
  63. package/examples/qk-next/components/reactbits/blocks/Backgrounds/LightRays/LightRays.tsx +474 -0
  64. package/examples/qk-next/components/theme-provider.tsx +11 -0
  65. package/examples/qk-next/components/ui/card.tsx +92 -0
  66. package/examples/qk-next/components/ui/command.tsx +184 -0
  67. package/examples/qk-next/components/ui/dialog.tsx +143 -0
  68. package/examples/qk-next/components/ui/drawer.tsx +135 -0
  69. package/examples/qk-next/components/ui/hover-card.tsx +44 -0
  70. package/examples/qk-next/components/ui/icons.tsx +148 -0
  71. package/examples/qk-next/components/ui/sonner.tsx +26 -0
  72. package/examples/qk-next/components/ui/table.tsx +117 -0
  73. package/examples/qk-next/components.json +21 -0
  74. package/examples/qk-next/eslint.config.mjs +21 -0
  75. package/examples/qk-next/jsrepo.json +13 -0
  76. package/examples/qk-next/lib/utils.ts +6 -0
  77. package/examples/qk-next/next.config.ts +8 -0
  78. package/examples/qk-next/package.json +48 -0
  79. package/examples/qk-next/pnpm-lock.yaml +5558 -0
  80. package/examples/qk-next/postcss.config.mjs +5 -0
  81. package/examples/qk-next/public/file.svg +1 -0
  82. package/examples/qk-next/public/globe.svg +1 -0
  83. package/examples/qk-next/public/next.svg +1 -0
  84. package/examples/qk-next/public/vercel.svg +1 -0
  85. package/examples/qk-next/public/window.svg +1 -0
  86. package/examples/qk-next/tsconfig.json +42 -0
  87. package/examples/qk-next/types/sonner.d.ts +3 -0
  88. package/jest.config.js +26 -0
  89. package/package.json +51 -0
  90. package/src/adapters/drizzle/drizzle-adapter.test.ts +115 -0
  91. package/src/adapters/drizzle/index.ts +299 -0
  92. package/src/adapters/index.ts +11 -0
  93. package/src/adapters/types.ts +72 -0
  94. package/src/index.ts +194 -0
  95. package/src/integration.test.ts +202 -0
  96. package/src/parser/index.ts +2 -0
  97. package/src/parser/parser.test.ts +1056 -0
  98. package/src/parser/parser.ts +268 -0
  99. package/src/parser/types.ts +97 -0
  100. package/src/query/builder.test.ts +272 -0
  101. package/src/query/builder.ts +274 -0
  102. package/src/query/index.ts +2 -0
  103. package/src/query/types.ts +107 -0
  104. package/src/security/index.ts +2 -0
  105. package/src/security/types.ts +210 -0
  106. package/src/security/validator.test.ts +459 -0
  107. package/src/security/validator.ts +395 -0
  108. package/src/security.test.ts +366 -0
  109. package/src/translators/drizzle/drizzle-translator.test.ts +128 -0
  110. package/src/translators/drizzle/index.test.ts +45 -0
  111. package/src/translators/drizzle/index.ts +346 -0
  112. package/src/translators/index.ts +14 -0
  113. package/src/translators/sql/index.test.ts +45 -0
  114. package/src/translators/sql/index.ts +331 -0
  115. package/src/translators/sql/sql-translator.test.ts +419 -0
  116. package/src/translators/types.ts +44 -0
  117. package/src/types/sonner.d.ts +3 -0
  118. package/tsconfig.json +34 -0
@@ -0,0 +1,419 @@
1
+ import { QueryParser } from '../../parser';
2
+ import { QueryExpression } from '../../parser/types';
3
+ import {
4
+ SqlTranslator,
5
+ SqlTranslationError,
6
+ ISqlTranslationResult
7
+ } from './index';
8
+
9
+ describe('SqlTranslator', () => {
10
+ const translator = new SqlTranslator({ useParameters: false });
11
+ const parser = new QueryParser();
12
+
13
+ describe('translate', () => {
14
+ it('should translate a simple equality expression', () => {
15
+ const expression = parser.parse('name:"John"');
16
+ const result = translator.translate(expression) as string;
17
+
18
+ expect(result).toBe('"name" = \'John\'');
19
+ });
20
+
21
+ it('should translate a comparison expression', () => {
22
+ const expression = parser.parse('priority:>18');
23
+ const result = translator.translate(expression) as string;
24
+
25
+ expect(result).toBe('"priority" > 18');
26
+ });
27
+
28
+ it('should translate numeric values directly', () => {
29
+ const expression = parser.parse('count:42');
30
+ const result = translator.translate(expression) as string;
31
+
32
+ expect(result).toBe('"count" = 42');
33
+ });
34
+
35
+ it('should translate boolean values correctly', () => {
36
+ const expression = parser.parse('active:true');
37
+ const result = translator.translate(expression) as string;
38
+
39
+ expect(result).toBe('"active" = true');
40
+ });
41
+
42
+ it('should translate a simple comparison expression', () => {
43
+ const expression = parser.parse('priority:>2');
44
+ const result = translator.translate(expression) as string;
45
+
46
+ expect(result).toBe('"priority" > 2');
47
+ });
48
+
49
+ it('should translate a logical AND expression', () => {
50
+ const expression = parser.parse('priority:>2 AND status:"active"');
51
+ const result = translator.translate(expression) as string;
52
+
53
+ expect(result).toBe('("priority" > 2) AND ("status" = \'active\')');
54
+ });
55
+
56
+ it('should translate a logical OR expression', () => {
57
+ const expression = parser.parse('status:"active" OR status:"pending"');
58
+ const result = translator.translate(expression) as string;
59
+
60
+ expect(result).toBe(
61
+ '("status" = \'active\') OR ("status" = \'pending\')'
62
+ );
63
+ });
64
+
65
+ it('should translate a NOT expression', () => {
66
+ const expression = parser.parse('NOT status:"inactive"');
67
+ const result = translator.translate(expression) as string;
68
+
69
+ expect(result).toBe('NOT ("status" = \'inactive\')');
70
+ });
71
+
72
+ it('should translate a complex nested expression', () => {
73
+ const expression = parser.parse(
74
+ '(priority:>2 AND status:"active") OR (role:"admin")'
75
+ );
76
+ const result = translator.translate(expression) as string;
77
+
78
+ expect(result).toBe(
79
+ '(("priority" > 2) AND ("status" = \'active\')) OR ("role" = \'admin\')'
80
+ );
81
+ });
82
+
83
+ it('should handle null values correctly', () => {
84
+ // Create a comparison expression with null value
85
+ const expression: QueryExpression = {
86
+ type: 'comparison',
87
+ field: 'last_login',
88
+ operator: '==',
89
+ value: null
90
+ };
91
+
92
+ const result = translator.translate(expression) as string;
93
+ expect(result).toBe('"last_login" IS NULL');
94
+
95
+ // Not equals with null
96
+ const notNullExpression: QueryExpression = {
97
+ type: 'comparison',
98
+ field: 'last_login',
99
+ operator: '!=',
100
+ value: null
101
+ };
102
+
103
+ const notNullResult = translator.translate(notNullExpression) as string;
104
+ expect(notNullResult).toBe('"last_login" IS NOT NULL');
105
+ });
106
+
107
+ it('should handle IN operator with array values', () => {
108
+ // Create an IN expression
109
+ const expression: QueryExpression = {
110
+ type: 'comparison',
111
+ field: 'status',
112
+ operator: 'IN',
113
+ value: ['active', 'pending', 'reviewing']
114
+ };
115
+
116
+ const result = translator.translate(expression) as string;
117
+ expect(result).toBe("\"status\" IN ('active', 'pending', 'reviewing')");
118
+ });
119
+
120
+ it('should handle NOT IN operator with array values', () => {
121
+ // Create a NOT IN expression
122
+ const expression: QueryExpression = {
123
+ type: 'comparison',
124
+ field: 'status',
125
+ operator: 'NOT IN',
126
+ value: ['inactive', 'closed']
127
+ };
128
+
129
+ const result = translator.translate(expression) as string;
130
+ expect(result).toBe("\"status\" NOT IN ('inactive', 'closed')");
131
+ });
132
+
133
+ it('should handle empty IN arrays properly', () => {
134
+ const expression: QueryExpression = {
135
+ type: 'comparison',
136
+ field: 'status',
137
+ operator: 'IN',
138
+ value: []
139
+ };
140
+
141
+ const result = translator.translate(expression) as string;
142
+ expect(result).toBe('FALSE');
143
+ });
144
+
145
+ it('should handle empty NOT IN arrays properly', () => {
146
+ const expression: QueryExpression = {
147
+ type: 'comparison',
148
+ field: 'status',
149
+ operator: 'NOT IN',
150
+ value: []
151
+ };
152
+
153
+ const result = translator.translate(expression) as string;
154
+ expect(result).toBe('TRUE');
155
+ });
156
+
157
+ it('should handle table.column notation', () => {
158
+ const expression = parser.parse('users.priority:>2');
159
+ const result = translator.translate(expression) as string;
160
+
161
+ expect(result).toBe('"users"."priority" > 2');
162
+ });
163
+
164
+ it('should properly escape string values with quotes', () => {
165
+ const expression = parser.parse('message:"It\'s a test"');
166
+ const result = translator.translate(expression) as string;
167
+
168
+ expect(result).toBe("\"message\" = 'It''s a test'");
169
+ });
170
+
171
+ it('should translate a combined expression with AND', () => {
172
+ const expression = parser.parse('priority:>2 AND status:"active"');
173
+ const sql = translator.translate(expression);
174
+
175
+ expect(sql).toContain('"priority" > 2');
176
+ expect(sql).toContain('"status" = ');
177
+ expect(sql).toContain('AND');
178
+ });
179
+ });
180
+
181
+ describe('with different quote options', () => {
182
+ it('should use custom identifier quotes', () => {
183
+ const mysqlTranslator = new SqlTranslator({
184
+ identifierQuote: '`',
185
+ useParameters: false
186
+ });
187
+
188
+ const expression = parser.parse('name:"John"');
189
+ const result = mysqlTranslator.translate(expression) as string;
190
+
191
+ expect(result).toBe("`name` = 'John'");
192
+ });
193
+
194
+ it('should use custom string literal quotes', () => {
195
+ const mssqlTranslator = new SqlTranslator({
196
+ stringLiteralQuote: '"',
197
+ useParameters: false
198
+ });
199
+
200
+ const expression = parser.parse('name:"John"');
201
+ const result = mssqlTranslator.translate(expression) as string;
202
+
203
+ expect(result).toBe('"name" = "John"');
204
+ });
205
+ });
206
+
207
+ describe('with parameters', () => {
208
+ const paramTranslator = new SqlTranslator({
209
+ useParameters: true
210
+ });
211
+
212
+ it('should use parameters for simple values', () => {
213
+ const expression = parser.parse('name:"John"');
214
+ const result = paramTranslator.translate(
215
+ expression
216
+ ) as ISqlTranslationResult;
217
+
218
+ expect(result.sql).toBe('"name" = ?');
219
+ expect(result.params).toEqual(['John']);
220
+ });
221
+
222
+ it('should use parameters for comparison operators', () => {
223
+ const expression = parser.parse('priority:>18');
224
+ const result = paramTranslator.translate(
225
+ expression
226
+ ) as ISqlTranslationResult;
227
+
228
+ expect(result.sql).toBe('"priority" > ?');
229
+ expect(result.params).toEqual([18]);
230
+ });
231
+
232
+ it('should use parameters for complex expressions', () => {
233
+ const expression = parser.parse(
234
+ '(priority:>18 AND status:"active") OR (role:"admin")'
235
+ );
236
+ const result = paramTranslator.translate(
237
+ expression
238
+ ) as ISqlTranslationResult;
239
+
240
+ expect(result.sql).toBe(
241
+ '(("priority" > ?) AND ("status" = ?)) OR ("role" = ?)'
242
+ );
243
+ expect(result.params).toEqual([18, 'active', 'admin']);
244
+ });
245
+
246
+ it('should use parameters for IN clauses', () => {
247
+ const expression: QueryExpression = {
248
+ type: 'comparison',
249
+ field: 'status',
250
+ operator: 'IN',
251
+ value: ['active', 'pending']
252
+ };
253
+
254
+ const result = paramTranslator.translate(
255
+ expression
256
+ ) as ISqlTranslationResult;
257
+ expect(result.sql).toBe('"status" IN (?, ?)');
258
+ expect(result.params).toEqual(['active', 'pending']);
259
+ });
260
+ });
261
+
262
+ describe('with field mappings', () => {
263
+ const mappingTranslator = new SqlTranslator({
264
+ fieldMappings: {
265
+ user_id: 'users.id',
266
+ user_name: 'users.name'
267
+ },
268
+ useParameters: false
269
+ });
270
+
271
+ it('should apply field mappings', () => {
272
+ const expression = parser.parse('user_id:123');
273
+ const result = mappingTranslator.translate(expression) as string;
274
+
275
+ expect(result).toBe('"users"."id" = 123');
276
+ });
277
+
278
+ it('should apply field mappings in complex expressions', () => {
279
+ const expression = parser.parse('user_id:>100 AND user_name:"John"');
280
+ const result = mappingTranslator.translate(expression) as string;
281
+
282
+ expect(result).toBe(
283
+ '("users"."id" > 100) AND ("users"."name" = \'John\')'
284
+ );
285
+ });
286
+ });
287
+
288
+ describe('with table prefixes', () => {
289
+ it('should preserve table prefixes in field names', () => {
290
+ const expression = parser.parse('users.priority:>2');
291
+ const sql = translator.translate(expression);
292
+
293
+ expect(sql).toContain('"users"."priority"');
294
+ });
295
+ });
296
+
297
+ describe('canTranslate', () => {
298
+ it('should return true for valid expressions', () => {
299
+ const expression = parser.parse('priority:>2');
300
+ expect(translator.canTranslate(expression)).toBe(true);
301
+ });
302
+
303
+ it('should return false for unsupported expressions', () => {
304
+ // Create a malformed expression to test error handling
305
+ const invalidExpression = {
306
+ type: 'unsupported'
307
+ } as unknown as QueryExpression;
308
+ expect(translator.canTranslate(invalidExpression)).toBe(false);
309
+ });
310
+
311
+ it('should return false for invalid operators', () => {
312
+ const invalidExpression = {
313
+ type: 'comparison',
314
+ field: 'name',
315
+ operator: '=~', // Invalid operator
316
+ value: 'pattern'
317
+ } as unknown as QueryExpression;
318
+
319
+ expect(translator.canTranslate(invalidExpression)).toBe(false);
320
+ });
321
+ });
322
+
323
+ describe('error handling', () => {
324
+ it('should throw error for empty expressions', () => {
325
+ expect(() =>
326
+ translator.translate(null as unknown as QueryExpression)
327
+ ).toThrow(SqlTranslationError);
328
+ });
329
+
330
+ it('should throw error for invalid expression types', () => {
331
+ const invalidExpression = {
332
+ type: 'unknown'
333
+ } as unknown as QueryExpression;
334
+
335
+ expect(() => translator.translate(invalidExpression)).toThrow(
336
+ SqlTranslationError
337
+ );
338
+ });
339
+
340
+ it('should throw error for invalid operator', () => {
341
+ const invalidExpression = {
342
+ type: 'comparison',
343
+ field: 'name',
344
+ operator: '=~', // Invalid operator
345
+ value: 'pattern'
346
+ } as unknown as QueryExpression;
347
+
348
+ expect(() => translator.translate(invalidExpression)).toThrow(
349
+ SqlTranslationError
350
+ );
351
+ });
352
+
353
+ it('should throw error for IN operator with non-array value', () => {
354
+ const invalidExpression: QueryExpression = {
355
+ type: 'comparison',
356
+ field: 'status',
357
+ operator: 'IN',
358
+ value: 'not-an-array'
359
+ };
360
+
361
+ expect(() => translator.translate(invalidExpression)).toThrow(
362
+ SqlTranslationError
363
+ );
364
+ });
365
+
366
+ it('should throw error for invalid value types', () => {
367
+ // Access the private formatValue method for direct testing
368
+ const formatValue = translator['formatValue'].bind(translator);
369
+
370
+ // Test with a complex object (which is not a valid QueryValue but might be passed incorrectly)
371
+ expect(() => formatValue({ complex: 'object' })).toThrow(
372
+ SqlTranslationError
373
+ );
374
+
375
+ // Test with a symbol (also not a valid QueryValue)
376
+ expect(() => formatValue(Symbol('test'))).toThrow(SqlTranslationError);
377
+ });
378
+
379
+ it('should throw error for logical expressions without right operand', () => {
380
+ const invalidExpression: QueryExpression = {
381
+ type: 'logical',
382
+ operator: 'AND',
383
+ left: {
384
+ type: 'comparison',
385
+ field: 'name',
386
+ operator: '==',
387
+ value: 'John'
388
+ }
389
+ // Missing right operand
390
+ };
391
+
392
+ expect(() => translator.translate(invalidExpression)).toThrow(
393
+ SqlTranslationError
394
+ );
395
+ });
396
+ });
397
+
398
+ describe('with options', () => {
399
+ it('should allow customization of output format', () => {
400
+ const expression = parser.parse('priority:>2');
401
+ const paramTranslator = new SqlTranslator({ useParameters: true });
402
+ const result = paramTranslator.translate(expression);
403
+
404
+ expect(typeof result).toBe('object');
405
+ });
406
+
407
+ it('should handle complex expressions with parameterization', () => {
408
+ const expr = '(priority:>2 AND status:"active") OR (role:"admin")';
409
+ const expression = parser.parse(expr);
410
+ const paramTranslator = new SqlTranslator({ useParameters: true });
411
+ const result = paramTranslator.translate(
412
+ expression
413
+ ) as ISqlTranslationResult;
414
+
415
+ expect(typeof result).toBe('object');
416
+ expect(Array.isArray(result.params)).toBe(true);
417
+ });
418
+ });
419
+ });
@@ -0,0 +1,44 @@
1
+ /**
2
+ * QueryKit Translator Types
3
+ *
4
+ * These are the core interfaces for translators, which convert QueryKit's
5
+ * internal AST representation into formats that specific data sources can understand.
6
+ */
7
+
8
+ import { QueryExpression } from '../parser/types';
9
+
10
+ /**
11
+ * Options for configuring a translator
12
+ */
13
+ export interface ITranslatorOptions {
14
+ /**
15
+ * Whether to normalize field names (e.g., lowercase them)
16
+ */
17
+ normalizeFieldNames?: boolean;
18
+
19
+ /**
20
+ * Custom field mappings from QueryKit fields to target fields
21
+ */
22
+ fieldMappings?: Record<string, string>;
23
+ }
24
+
25
+ /**
26
+ * Interface for a query translator
27
+ */
28
+ export interface ITranslator<T = unknown> {
29
+ /**
30
+ * Translate a QueryKit expression into the target format
31
+ *
32
+ * @param expression The QueryKit expression to translate
33
+ * @returns The translated query in the target format
34
+ */
35
+ translate(expression: QueryExpression): T;
36
+
37
+ /**
38
+ * Check if an expression can be translated
39
+ *
40
+ * @param expression The expression to check
41
+ * @returns true if the expression can be translated, false otherwise
42
+ */
43
+ canTranslate(expression: QueryExpression): boolean;
44
+ }
@@ -0,0 +1,3 @@
1
+ declare module 'sonner' {
2
+ export function toast(message: string): void;
3
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "commonjs",
5
+ "lib": ["ES2020"],
6
+ "declaration": true,
7
+ "outDir": "./dist",
8
+ "rootDir": "./src",
9
+ "strict": true,
10
+ "noImplicitAny": true,
11
+ "strictNullChecks": true,
12
+ "strictFunctionTypes": true,
13
+ "strictBindCallApply": true,
14
+ "strictPropertyInitialization": true,
15
+ "noImplicitThis": true,
16
+ "alwaysStrict": true,
17
+ "noUnusedLocals": true,
18
+ "noUnusedParameters": true,
19
+ "noImplicitReturns": true,
20
+ "noFallthroughCasesInSwitch": true,
21
+ "moduleResolution": "node",
22
+ "baseUrl": "./",
23
+ "paths": {
24
+ "*": ["node_modules/*"]
25
+ },
26
+ "esModuleInterop": true,
27
+ "experimentalDecorators": true,
28
+ "emitDecoratorMetadata": true,
29
+ "skipLibCheck": true,
30
+ "forceConsistentCasingInFileNames": true
31
+ },
32
+ "include": ["src/**/*"],
33
+ "exclude": ["node_modules", "**/*.test.ts"]
34
+ }