@gblikas/querykit 0.3.0 → 0.4.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/.husky/pre-commit +3 -3
- package/README.md +347 -0
- package/dist/index.d.ts +36 -3
- package/dist/index.js +20 -3
- package/dist/virtual-fields/index.d.ts +5 -0
- package/dist/virtual-fields/index.js +21 -0
- package/dist/virtual-fields/resolver.d.ts +17 -0
- package/dist/virtual-fields/resolver.js +107 -0
- package/dist/virtual-fields/types.d.ts +160 -0
- package/dist/virtual-fields/types.js +5 -0
- package/examples/qk-next/app/page.tsx +184 -85
- package/examples/qk-next/package.json +1 -1
- package/package.json +2 -2
- package/src/adapters/drizzle/index.ts +3 -3
- package/src/index.ts +77 -8
- package/src/virtual-fields/index.ts +6 -0
- package/src/virtual-fields/integration.test.ts +338 -0
- package/src/virtual-fields/resolver.ts +165 -0
- package/src/virtual-fields/types.ts +203 -0
- package/src/virtual-fields/virtual-fields.test.ts +831 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for Virtual Fields support
|
|
3
|
+
*/
|
|
4
|
+
import { QueryExpression, IComparisonExpression, ComparisonOperator } from '../parser/types';
|
|
5
|
+
/**
|
|
6
|
+
* Base interface for query context.
|
|
7
|
+
* Users can extend this interface with their own context properties.
|
|
8
|
+
*/
|
|
9
|
+
export interface IQueryContext {
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Input provided to a virtual field resolver.
|
|
14
|
+
* Contains the parsed field, operator, and value from the query.
|
|
15
|
+
*/
|
|
16
|
+
export interface IVirtualFieldInput {
|
|
17
|
+
/**
|
|
18
|
+
* The virtual field name (e.g., "my")
|
|
19
|
+
*/
|
|
20
|
+
field: string;
|
|
21
|
+
/**
|
|
22
|
+
* The comparison operator used (e.g., ":", ">", "<", etc.)
|
|
23
|
+
* Maps to ComparisonOperator type
|
|
24
|
+
*/
|
|
25
|
+
operator: string;
|
|
26
|
+
/**
|
|
27
|
+
* The value provided in the query
|
|
28
|
+
*/
|
|
29
|
+
value: string;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Helper type to filter out index signatures from a type
|
|
33
|
+
*/
|
|
34
|
+
type KnownKeys<T> = {
|
|
35
|
+
[K in keyof T]: string extends K ? never : number extends K ? never : K;
|
|
36
|
+
} extends {
|
|
37
|
+
[_ in keyof T]: infer U;
|
|
38
|
+
} ? U : never;
|
|
39
|
+
/**
|
|
40
|
+
* Utility type to extract all field names from a schema.
|
|
41
|
+
* Recursively extracts field names from nested tables, excluding index signatures.
|
|
42
|
+
*/
|
|
43
|
+
export type AllSchemaFields<TSchema extends Record<string, object>> = {
|
|
44
|
+
[K in KnownKeys<TSchema>]: TSchema[K] extends {
|
|
45
|
+
[key: string]: unknown;
|
|
46
|
+
} ? keyof TSchema[K] & string : never;
|
|
47
|
+
}[KnownKeys<TSchema>];
|
|
48
|
+
/**
|
|
49
|
+
* Type-safe mapping from allowed values to schema fields.
|
|
50
|
+
* Ensures all keys in TKeys map to valid fields in the schema.
|
|
51
|
+
*/
|
|
52
|
+
export type SchemaFieldMap<TKeys extends string, TSchema extends Record<string, object>> = Record<TKeys, AllSchemaFields<TSchema>>;
|
|
53
|
+
/**
|
|
54
|
+
* Helper functions provided to virtual field resolvers.
|
|
55
|
+
*
|
|
56
|
+
* Note: The fields() method is generic at the method level, not the interface level.
|
|
57
|
+
* This allows TypeScript to infer TValues from the mapping object passed at call-time,
|
|
58
|
+
* eliminating the need for type assertions while maintaining full type safety.
|
|
59
|
+
*/
|
|
60
|
+
export interface IResolverHelpers<TSchema extends Record<string, object>> {
|
|
61
|
+
/**
|
|
62
|
+
* Type-safe field mapping helper.
|
|
63
|
+
* Ensures all allowedValues are mapped to valid schema fields.
|
|
64
|
+
*
|
|
65
|
+
* The generic TValues parameter is inferred from the keys in the mapping object,
|
|
66
|
+
* providing full type safety without requiring explicit type annotations.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* const fieldMap = fields({
|
|
70
|
+
* assigned: 'assignee_id',
|
|
71
|
+
* created: 'creator_id'
|
|
72
|
+
* });
|
|
73
|
+
* // TypeScript infers TValues as 'assigned' | 'created'
|
|
74
|
+
*/
|
|
75
|
+
fields: <TValues extends string>(mapping: SchemaFieldMap<TValues, TSchema>) => SchemaFieldMap<TValues, TSchema>;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Schema-constrained comparison expression.
|
|
79
|
+
* Ensures field names are valid schema fields.
|
|
80
|
+
*/
|
|
81
|
+
export interface ITypedComparisonExpression<TFields extends string = string> extends Omit<IComparisonExpression, 'field'> {
|
|
82
|
+
type: 'comparison';
|
|
83
|
+
field: TFields;
|
|
84
|
+
operator: ComparisonOperator;
|
|
85
|
+
value: string | number | boolean | null | Array<string | number | boolean | null>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Schema-constrained query expression.
|
|
89
|
+
* Can be a comparison or logical expression with typed fields.
|
|
90
|
+
*/
|
|
91
|
+
export type ITypedQueryExpression<TFields extends string = string> = ITypedComparisonExpression<TFields> | QueryExpression;
|
|
92
|
+
/**
|
|
93
|
+
* Definition for a virtual field.
|
|
94
|
+
* Configures how a virtual field should be resolved at query execution time.
|
|
95
|
+
*/
|
|
96
|
+
export interface IVirtualFieldDefinition<TSchema extends Record<string, object>, TContext extends IQueryContext = IQueryContext, TValues extends string = string> {
|
|
97
|
+
/**
|
|
98
|
+
* Allowed values for this virtual field.
|
|
99
|
+
* Use `as const` for type inference.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* allowedValues: ['assigned', 'created', 'watching'] as const
|
|
103
|
+
*/
|
|
104
|
+
allowedValues: readonly TValues[];
|
|
105
|
+
/**
|
|
106
|
+
* Whether to allow comparison operators beyond `:` (equality).
|
|
107
|
+
* If false, only `:` is allowed. If true, `:>`, `:<`, etc. are permitted.
|
|
108
|
+
*
|
|
109
|
+
* @default false
|
|
110
|
+
*/
|
|
111
|
+
allowOperators?: boolean;
|
|
112
|
+
/**
|
|
113
|
+
* Resolve the virtual field to a real query expression.
|
|
114
|
+
* The `fields` helper ensures type-safe field references.
|
|
115
|
+
*
|
|
116
|
+
* @param input - The parsed virtual field input (field, operator, value)
|
|
117
|
+
* @param context - Runtime context provided by createContext()
|
|
118
|
+
* @param helpers - Helper functions including type-safe fields() helper
|
|
119
|
+
* @returns A query expression that replaces the virtual field
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* resolve: (input, ctx, { fields }) => {
|
|
123
|
+
* const fieldMap = fields({
|
|
124
|
+
* assigned: 'assignee_id',
|
|
125
|
+
* created: 'creator_id'
|
|
126
|
+
* });
|
|
127
|
+
* return {
|
|
128
|
+
* type: 'comparison',
|
|
129
|
+
* field: fieldMap[input.value],
|
|
130
|
+
* operator: '==',
|
|
131
|
+
* value: ctx.currentUserId
|
|
132
|
+
* };
|
|
133
|
+
* }
|
|
134
|
+
*/
|
|
135
|
+
resolve: (input: IVirtualFieldInput & {
|
|
136
|
+
value: TValues;
|
|
137
|
+
}, context: TContext, helpers: IResolverHelpers<TSchema>) => ITypedQueryExpression<AllSchemaFields<TSchema>>;
|
|
138
|
+
/**
|
|
139
|
+
* Human-readable description (for autocomplete UI).
|
|
140
|
+
* Optional metadata for documentation and tooling.
|
|
141
|
+
*/
|
|
142
|
+
description?: string;
|
|
143
|
+
/**
|
|
144
|
+
* Descriptions for each allowed value (for autocomplete UI).
|
|
145
|
+
* Optional metadata for documentation and tooling.
|
|
146
|
+
*/
|
|
147
|
+
valueDescriptions?: Partial<Record<TValues, string>>;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Configuration for all virtual fields in a QueryKit instance.
|
|
151
|
+
*
|
|
152
|
+
* Note: Uses a flexible type for the values to allow each virtual field definition
|
|
153
|
+
* to have its own specific TValues type (e.g., 'assigned' | 'created' for one field,
|
|
154
|
+
* 'high' | 'low' for another). The IResolverHelpers.fields() method infers these
|
|
155
|
+
* types at call-time, maintaining type safety without needing explicit annotations.
|
|
156
|
+
*/
|
|
157
|
+
export type VirtualFieldsConfig<TSchema extends Record<string, object> = Record<string, object>, TContext extends IQueryContext = IQueryContext> = {
|
|
158
|
+
[fieldName: string]: IVirtualFieldDefinition<TSchema, TContext, string>;
|
|
159
|
+
};
|
|
160
|
+
export {};
|
|
@@ -4,7 +4,7 @@ import { useEffect, useMemo, useState, useCallback, useRef, JSX } from 'react';
|
|
|
4
4
|
import { drizzle } from 'drizzle-orm/pglite';
|
|
5
5
|
import { usePGlite } from '@electric-sql/pglite-react';
|
|
6
6
|
import { pgTable, serial, text, integer, boolean } from 'drizzle-orm/pg-core';
|
|
7
|
-
import { InferSelectModel, sql
|
|
7
|
+
import { InferSelectModel, sql } from 'drizzle-orm';
|
|
8
8
|
import { Card, CardContent } from '@/components/ui/card';
|
|
9
9
|
import {
|
|
10
10
|
Table,
|
|
@@ -17,9 +17,9 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
QueryParser,
|
|
19
19
|
SqlTranslator,
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
createDrizzleQueryKit,
|
|
21
|
+
ISecurityOptions,
|
|
22
|
+
IQueryStructure
|
|
23
23
|
} from '@gblikas/querykit';
|
|
24
24
|
import { Copy, Check, Search, ChevronUp, FileCode, X } from 'lucide-react';
|
|
25
25
|
import { Light as SyntaxHighlighter } from 'react-syntax-highlighter';
|
|
@@ -75,7 +75,7 @@ const highlightQueryHtml = (input: string): string => {
|
|
|
75
75
|
.join('');
|
|
76
76
|
};
|
|
77
77
|
|
|
78
|
-
const INSTALL_SNIPPET = `pnpm i @gblikas/querykit drizzle-orm`;
|
|
78
|
+
const INSTALL_SNIPPET = `pnpm i @gblikas/querykit@0.3.0 drizzle-orm`;
|
|
79
79
|
|
|
80
80
|
const SCHEMA_SNIPPET = `// schema.ts
|
|
81
81
|
import { serial, text, pgTable } from 'drizzle-orm/pg-core';
|
|
@@ -91,25 +91,30 @@ export type SelectUser = InferSelectModel<typeof users>;
|
|
|
91
91
|
`;
|
|
92
92
|
|
|
93
93
|
const QUERYKIT_SNIPPET = `// querykit.ts
|
|
94
|
-
import {
|
|
95
|
-
import {
|
|
94
|
+
import { createDrizzleQueryKit } from '@gblikas/querykit';
|
|
95
|
+
import { db } from './db';
|
|
96
96
|
import { users } from './schema';
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
// New 0.3.0: createDrizzleQueryKit combines adapter & security config
|
|
99
|
+
export const qk = createDrizzleQueryKit({
|
|
100
|
+
db,
|
|
100
101
|
schema: { users },
|
|
102
|
+
security: {
|
|
103
|
+
maxQueryDepth: 5,
|
|
104
|
+
maxClauseCount: 20,
|
|
105
|
+
sanitizeWildcards: true,
|
|
106
|
+
},
|
|
101
107
|
});
|
|
102
108
|
|
|
103
109
|
// example.ts
|
|
104
110
|
import { qk } from './querykit';
|
|
105
111
|
|
|
106
|
-
const
|
|
112
|
+
const results = await qk
|
|
107
113
|
.query('users')
|
|
108
114
|
.where('status:done AND name:"John *"')
|
|
109
115
|
.orderBy('name', 'asc')
|
|
110
|
-
.limit(10)
|
|
111
|
-
|
|
112
|
-
const results = await query.execute();
|
|
116
|
+
.limit(10)
|
|
117
|
+
.execute();
|
|
113
118
|
`;
|
|
114
119
|
|
|
115
120
|
const tasks = pgTable('tasks', {
|
|
@@ -137,6 +142,9 @@ export default function Home(): JSX.Element {
|
|
|
137
142
|
const [, setLastExecutionMs] = useState<number | null>(null);
|
|
138
143
|
const [rowsScanned, setRowsScanned] = useState<number | null>(null);
|
|
139
144
|
const [operatorsUsed, setOperatorsUsed] = useState<string[]>([]);
|
|
145
|
+
const [queryStructure, setQueryStructure] = useState<IQueryStructure | null>(
|
|
146
|
+
null
|
|
147
|
+
);
|
|
140
148
|
const [usedQueryKit, setUsedQueryKit] = useState<boolean>(false);
|
|
141
149
|
const [, setExplainJson] = useState<string | null>(null);
|
|
142
150
|
const [, setPlanningTimeMs] = useState<number | null>(null);
|
|
@@ -314,23 +322,33 @@ export default function Home(): JSX.Element {
|
|
|
314
322
|
void seed();
|
|
315
323
|
}, [db]);
|
|
316
324
|
|
|
325
|
+
// Security configuration for QueryKit 0.3.0
|
|
326
|
+
const securityOptions: ISecurityOptions = useMemo(
|
|
327
|
+
() => ({
|
|
328
|
+
maxQueryDepth: 5,
|
|
329
|
+
maxClauseCount: 20,
|
|
330
|
+
allowDotNotation: false, // Disable dot notation for simple flat schema
|
|
331
|
+
sanitizeWildcards: true
|
|
332
|
+
}),
|
|
333
|
+
[]
|
|
334
|
+
);
|
|
335
|
+
|
|
317
336
|
const parser = useMemo(() => new QueryParser(), []);
|
|
318
337
|
const sqlTranslator = useMemo(
|
|
319
338
|
() => new SqlTranslator({ useParameters: false }),
|
|
320
339
|
[]
|
|
321
340
|
);
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
}, [db]);
|
|
341
|
+
|
|
342
|
+
// Use the new createDrizzleQueryKit factory (0.3.0 feature)
|
|
343
|
+
const qk = useMemo(
|
|
344
|
+
() =>
|
|
345
|
+
createDrizzleQueryKit({
|
|
346
|
+
db,
|
|
347
|
+
schema: { tasks },
|
|
348
|
+
security: securityOptions
|
|
349
|
+
}),
|
|
350
|
+
[db, securityOptions]
|
|
351
|
+
);
|
|
334
352
|
|
|
335
353
|
// Note: Execute via QueryKit fluent API (Drizzle adapter under the hood)
|
|
336
354
|
|
|
@@ -346,6 +364,7 @@ export default function Home(): JSX.Element {
|
|
|
346
364
|
setIsInputFocused(false);
|
|
347
365
|
inputRef.current?.blur();
|
|
348
366
|
setOperatorsUsed([]);
|
|
367
|
+
setQueryStructure(null);
|
|
349
368
|
setExplainJson(null);
|
|
350
369
|
setPlanningTimeMs(null);
|
|
351
370
|
setExecutionTimeMs(null);
|
|
@@ -407,49 +426,77 @@ export default function Home(): JSX.Element {
|
|
|
407
426
|
if (searchQuery.trim()) {
|
|
408
427
|
try {
|
|
409
428
|
const parseStart = performance.now();
|
|
410
|
-
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
[
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
429
|
+
// Use parseWithContext for enhanced query analysis (0.3.0 feature)
|
|
430
|
+
const parseResult = parser.parseWithContext(searchQuery, {
|
|
431
|
+
schema: {
|
|
432
|
+
title: { type: 'string', description: 'Task title' },
|
|
433
|
+
status: {
|
|
434
|
+
type: 'string',
|
|
435
|
+
description: 'Task status',
|
|
436
|
+
allowedValues: ['todo', 'doing', 'done']
|
|
437
|
+
},
|
|
438
|
+
priority: { type: 'number', description: 'Priority level' },
|
|
439
|
+
completed: { type: 'boolean', description: 'Is completed' }
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
// Set query structure for UI display
|
|
444
|
+
setQueryStructure(parseResult.structure);
|
|
445
|
+
|
|
446
|
+
if (parseResult.success && parseResult.ast) {
|
|
447
|
+
const translated = sqlTranslator.translate(parseResult.ast) as
|
|
448
|
+
| string
|
|
449
|
+
| { sql: string; params: unknown[] };
|
|
450
|
+
localParseTranslateMs = performance.now() - parseStart;
|
|
451
|
+
setParseTranslateMs(localParseTranslateMs);
|
|
452
|
+
whereSql =
|
|
453
|
+
typeof translated === 'string' ? translated : translated.sql;
|
|
454
|
+
mockSQL += ` WHERE ${whereSql}`;
|
|
455
|
+
|
|
456
|
+
// Use referenced fields from structure (0.3.0 feature)
|
|
457
|
+
detectedOperators = [];
|
|
458
|
+
if (parseResult.structure.operatorCount > 0) {
|
|
459
|
+
// Extract operators from the SQL for display
|
|
460
|
+
const extractOperators = (sqlText: string): string[] => {
|
|
461
|
+
const found = new Set<string>();
|
|
462
|
+
const keywordOps: Array<[string, RegExp]> = [
|
|
463
|
+
['ILIKE', /\bILIKE\b/i],
|
|
464
|
+
['LIKE', /\bLIKE\b/i],
|
|
465
|
+
['AND', /\bAND\b/i],
|
|
466
|
+
['OR', /\bOR\b/i],
|
|
467
|
+
['NOT', /\bNOT\b/i],
|
|
468
|
+
['IN', /\bIN\b/i],
|
|
469
|
+
['BETWEEN', /\bBETWEEN\b/i]
|
|
470
|
+
];
|
|
471
|
+
for (const [name, re] of keywordOps) {
|
|
472
|
+
if (re.test(sqlText)) found.add(name);
|
|
473
|
+
}
|
|
474
|
+
let temp = sqlText.toUpperCase();
|
|
475
|
+
const consume = (re: RegExp, label: string): void => {
|
|
476
|
+
if (re.test(temp)) {
|
|
477
|
+
found.add(label);
|
|
478
|
+
temp = temp.replace(re, ' ');
|
|
479
|
+
}
|
|
480
|
+
};
|
|
481
|
+
consume(/>=/g, '>=');
|
|
482
|
+
consume(/<=/g, '<=');
|
|
483
|
+
consume(/!=/g, '!=');
|
|
484
|
+
consume(/=/g, '=');
|
|
485
|
+
consume(/>/g, '>');
|
|
486
|
+
consume(/</g, '<');
|
|
487
|
+
return Array.from(found);
|
|
488
|
+
};
|
|
489
|
+
detectedOperators = extractOperators(whereSql);
|
|
490
|
+
} else {
|
|
491
|
+
// Simple query - just detect from SQL
|
|
492
|
+
if (/ILIKE/i.test(whereSql)) detectedOperators.push('ILIKE');
|
|
493
|
+
if (/=/i.test(whereSql) && !/!=/i.test(whereSql))
|
|
494
|
+
detectedOperators.push('=');
|
|
435
495
|
}
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
found.add(label);
|
|
441
|
-
temp = temp.replace(re, ' ');
|
|
442
|
-
}
|
|
443
|
-
};
|
|
444
|
-
consume(/>=/g, '>=');
|
|
445
|
-
consume(/<=/g, '<=');
|
|
446
|
-
consume(/!=/g, '!=');
|
|
447
|
-
consume(/=/g, '=');
|
|
448
|
-
consume(/>/g, '>');
|
|
449
|
-
consume(/</g, '<');
|
|
450
|
-
return Array.from(found);
|
|
451
|
-
};
|
|
452
|
-
detectedOperators = extractOperators(whereSql);
|
|
496
|
+
} else {
|
|
497
|
+
// Parse failed - fall back to ILIKE search
|
|
498
|
+
throw new Error(parseResult.error?.message || 'Parse failed');
|
|
499
|
+
}
|
|
453
500
|
} catch (error) {
|
|
454
501
|
void trackQueryKitIssue({
|
|
455
502
|
errorName: (error as Error)?.name ?? 'UnknownError',
|
|
@@ -1013,25 +1060,77 @@ export default function Home(): JSX.Element {
|
|
|
1013
1060
|
)}
|
|
1014
1061
|
</div>
|
|
1015
1062
|
{!isShortViewport ? (
|
|
1016
|
-
|
|
1017
|
-
<div className="
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
{operatorsUsed.
|
|
1023
|
-
<
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1063
|
+
<>
|
|
1064
|
+
<div className="mt-3 grid grid-cols-2 gap-3">
|
|
1065
|
+
<div>
|
|
1066
|
+
<div className="text-xs text-muted-foreground mb-1">
|
|
1067
|
+
Detected operators
|
|
1068
|
+
</div>
|
|
1069
|
+
{operatorsUsed.length ? (
|
|
1070
|
+
<div className="flex flex-wrap gap-2">
|
|
1071
|
+
{operatorsUsed.map(op => (
|
|
1072
|
+
<span
|
|
1073
|
+
key={op}
|
|
1074
|
+
className="inline-flex items-center rounded-full border bg-muted px-2 py-0.5 text-xs font-medium"
|
|
1075
|
+
>
|
|
1076
|
+
{op}
|
|
1077
|
+
</span>
|
|
1078
|
+
))}
|
|
1079
|
+
</div>
|
|
1080
|
+
) : (
|
|
1081
|
+
<div className="text-xs text-muted-foreground">
|
|
1082
|
+
-
|
|
1083
|
+
</div>
|
|
1084
|
+
)}
|
|
1030
1085
|
</div>
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1086
|
+
{queryStructure && (
|
|
1087
|
+
<div>
|
|
1088
|
+
<div className="text-xs text-muted-foreground mb-1">
|
|
1089
|
+
Query complexity (0.3.0)
|
|
1090
|
+
</div>
|
|
1091
|
+
<div className="flex flex-wrap gap-2">
|
|
1092
|
+
<span
|
|
1093
|
+
className={cn(
|
|
1094
|
+
'inline-flex items-center rounded-full border px-2 py-0.5 text-xs font-medium',
|
|
1095
|
+
queryStructure.complexity === 'simple' &&
|
|
1096
|
+
'bg-green-500/10 text-green-600 border-green-500/30',
|
|
1097
|
+
queryStructure.complexity === 'moderate' &&
|
|
1098
|
+
'bg-yellow-500/10 text-yellow-600 border-yellow-500/30',
|
|
1099
|
+
queryStructure.complexity === 'complex' &&
|
|
1100
|
+
'bg-red-500/10 text-red-600 border-red-500/30'
|
|
1101
|
+
)}
|
|
1102
|
+
>
|
|
1103
|
+
{queryStructure.complexity}
|
|
1104
|
+
</span>
|
|
1105
|
+
<span className="inline-flex items-center rounded-full border bg-muted px-2 py-0.5 text-xs font-medium">
|
|
1106
|
+
depth: {queryStructure.depth}
|
|
1107
|
+
</span>
|
|
1108
|
+
<span className="inline-flex items-center rounded-full border bg-muted px-2 py-0.5 text-xs font-medium">
|
|
1109
|
+
clauses: {queryStructure.clauseCount}
|
|
1110
|
+
</span>
|
|
1111
|
+
</div>
|
|
1112
|
+
</div>
|
|
1113
|
+
)}
|
|
1114
|
+
</div>
|
|
1115
|
+
{queryStructure &&
|
|
1116
|
+
queryStructure.referencedFields.length > 0 && (
|
|
1117
|
+
<div className="mt-3">
|
|
1118
|
+
<div className="text-xs text-muted-foreground mb-1">
|
|
1119
|
+
Referenced fields
|
|
1120
|
+
</div>
|
|
1121
|
+
<div className="flex flex-wrap gap-2">
|
|
1122
|
+
{queryStructure.referencedFields.map(field => (
|
|
1123
|
+
<span
|
|
1124
|
+
key={field}
|
|
1125
|
+
className="inline-flex items-center rounded-full border bg-blue-500/10 text-blue-600 border-blue-500/30 px-2 py-0.5 text-xs font-medium"
|
|
1126
|
+
>
|
|
1127
|
+
{field}
|
|
1128
|
+
</span>
|
|
1129
|
+
))}
|
|
1130
|
+
</div>
|
|
1131
|
+
</div>
|
|
1132
|
+
)}
|
|
1133
|
+
</>
|
|
1035
1134
|
) : (
|
|
1036
1135
|
<div className="mt-3 text-xs text-muted-foreground">
|
|
1037
1136
|
View on larger screen for more details
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@electric-sql/pglite": "^0.3.7",
|
|
13
13
|
"@electric-sql/pglite-react": "^0.2.25",
|
|
14
|
-
"@gblikas/querykit": "^0.
|
|
14
|
+
"@gblikas/querykit": "^0.3.0",
|
|
15
15
|
"@radix-ui/react-dialog": "^1.1.15",
|
|
16
16
|
"@radix-ui/react-hover-card": "^1.1.15",
|
|
17
17
|
"@vercel/analytics": "^1.5.0",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gblikas/querykit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "A comprehensive query toolkit for TypeScript that simplifies building and executing data queries across different environments",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
"lint-staged": "^15.5.1",
|
|
44
44
|
"prettier": "^3.2.5",
|
|
45
45
|
"ts-jest": "^29.1.2",
|
|
46
|
-
"typescript": "^5.
|
|
46
|
+
"typescript": "^5.9.3"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
49
|
"build": "tsc --outDir dist",
|
|
@@ -9,6 +9,7 @@ import { IAdapter, IAdapterOptions, IQueryExecutionOptions } from '../types';
|
|
|
9
9
|
import { QueryExpression } from '../../parser/types';
|
|
10
10
|
import { SQL, SQLWrapper, asc, desc, sql } from 'drizzle-orm';
|
|
11
11
|
import { createQueryKit, QueryKit } from '../../index';
|
|
12
|
+
import { IQueryContext } from '../../virtual-fields';
|
|
12
13
|
/**
|
|
13
14
|
* Type for Drizzle ORM database instance
|
|
14
15
|
*/
|
|
@@ -91,8 +92,7 @@ export class DrizzleAdapterError extends Error {
|
|
|
91
92
|
*/
|
|
92
93
|
export class DrizzleAdapter<
|
|
93
94
|
TSchema extends Record<string, unknown> = Record<string, unknown>
|
|
94
|
-
> implements IAdapter<IDrizzleAdapterOptions<TSchema>>
|
|
95
|
-
{
|
|
95
|
+
> implements IAdapter<IDrizzleAdapterOptions<TSchema>> {
|
|
96
96
|
private db!: unknown;
|
|
97
97
|
private schema!: TSchema;
|
|
98
98
|
private translator!: DrizzleTranslator;
|
|
@@ -291,7 +291,7 @@ export function createDrizzleQueryKit<
|
|
|
291
291
|
|
|
292
292
|
type RowMap = RowMapFromDrizzleSchema<TSchema>;
|
|
293
293
|
|
|
294
|
-
return createQueryKit<TSchema, RowMap>({
|
|
294
|
+
return createQueryKit<TSchema, IQueryContext, RowMap>({
|
|
295
295
|
adapter,
|
|
296
296
|
schema: args.schema as unknown as TSchema,
|
|
297
297
|
security: args.security
|