@gblikas/querykit 0.1.0 → 0.3.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 +65 -2
- package/README.md +163 -1
- package/dist/parser/index.d.ts +1 -0
- package/dist/parser/index.js +1 -0
- package/dist/parser/input-parser.d.ts +215 -0
- package/dist/parser/input-parser.js +493 -0
- package/dist/parser/parser.d.ts +148 -1
- package/dist/parser/parser.js +880 -6
- package/dist/parser/types.d.ts +432 -0
- package/examples/qk-next/app/page.tsx +6 -1
- package/package.json +1 -1
- package/src/parser/divergence.test.ts +357 -0
- package/src/parser/index.ts +2 -1
- package/src/parser/input-parser.test.ts +770 -0
- package/src/parser/input-parser.ts +697 -0
- package/src/parser/parse-with-context-suggestions.test.ts +360 -0
- package/src/parser/parse-with-context-validation.test.ts +447 -0
- package/src/parser/parse-with-context.test.ts +325 -0
- package/src/parser/parser.test.ts +209 -1
- package/src/parser/parser.ts +1106 -25
- package/src/parser/token-consistency.test.ts +341 -0
- package/src/parser/types.ts +545 -23
- package/examples/qk-next/pnpm-lock.yaml +0 -5623
package/.cursor/BUGBOT.md
CHANGED
|
@@ -1,21 +1,84 @@
|
|
|
1
1
|
# Project review guidelines
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Below is a list of generally accepted best-practices to prevent bugs in QueryKit. Not all guidelines may apply to every component; please make sure to read the README.md for context on the project's goals.
|
|
4
4
|
|
|
5
5
|
## Security focus areas
|
|
6
6
|
|
|
7
7
|
- Validate user input in API endpoints
|
|
8
8
|
- Check for SQL injection vulnerabilities in database queries
|
|
9
9
|
- Ensure proper authentication on protected routes
|
|
10
|
+
- Validate query inputs using `parseWithContext` with security options
|
|
11
|
+
- Use `allowedFields` and `denyFields` to restrict queryable fields
|
|
12
|
+
- Set `maxQueryDepth` and `maxClauseCount` to prevent DoS attacks
|
|
13
|
+
|
|
14
|
+
### Query parsing security
|
|
15
|
+
|
|
16
|
+
When using the input parser or `parseWithContext`:
|
|
17
|
+
|
|
18
|
+
1. **Never trust user-provided queries** - Always validate with security options:
|
|
19
|
+
```typescript
|
|
20
|
+
const result = parser.parseWithContext(userQuery, {
|
|
21
|
+
securityOptions: {
|
|
22
|
+
allowedFields: ['name', 'status', 'priority'],
|
|
23
|
+
denyFields: ['password', 'secret'],
|
|
24
|
+
maxQueryDepth: 5,
|
|
25
|
+
maxClauseCount: 20
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
if (!result.security?.passed) {
|
|
30
|
+
// Reject query - contains violations
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
2. **Schema validation** - Use schema to detect typos and invalid fields early:
|
|
35
|
+
```typescript
|
|
36
|
+
const result = parser.parseWithContext(userQuery, { schema });
|
|
37
|
+
if (!result.fieldValidation?.valid) {
|
|
38
|
+
// Show user-friendly error with suggestions
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
3. **Input parser limitations** - The input parser (`parseQueryInput`, `parseQueryTokens`) is regex-based for performance. It may accept inputs that the main parser rejects. Always validate with `parseWithContext` or `parser.parse()` before executing queries.
|
|
10
43
|
|
|
11
44
|
## Architecture patterns
|
|
12
45
|
|
|
13
46
|
- Use dependency injection for services
|
|
14
47
|
- Follow the repository pattern for data access
|
|
15
48
|
- Implement proper error handling with custom error classes
|
|
49
|
+
- Parser components follow Single Responsibility Principle:
|
|
50
|
+
- `input-parser.ts` - Fast, regex-based tokenization for UI feedback
|
|
51
|
+
- `parser.ts` - Full Liqe-based parsing with AST generation
|
|
52
|
+
- `parseWithContext` - Orchestrates both for rich context
|
|
53
|
+
|
|
54
|
+
### Parser architecture
|
|
55
|
+
|
|
56
|
+
The parsing system has two tiers:
|
|
57
|
+
|
|
58
|
+
1. **Input Parser** (`parseQueryInput`, `parseQueryTokens`)
|
|
59
|
+
- Purpose: Real-time UI feedback (highlighting, cursor context)
|
|
60
|
+
- Performance: O(n) regex-based, no AST generation
|
|
61
|
+
- Error handling: Best-effort, never throws
|
|
62
|
+
- Use for: Search bar highlighting, autocomplete triggering
|
|
63
|
+
|
|
64
|
+
2. **Query Parser** (`parser.parse`, `parseWithContext`)
|
|
65
|
+
- Purpose: Query validation and execution
|
|
66
|
+
- Performance: Full Liqe grammar parsing
|
|
67
|
+
- Error handling: Strict validation, detailed error messages
|
|
68
|
+
- Use for: Query execution, security validation
|
|
16
69
|
|
|
17
70
|
## Common issues
|
|
18
71
|
|
|
19
72
|
- Memory leaks in React components (check useEffect cleanup)
|
|
20
73
|
- Missing error boundaries in UI components
|
|
21
|
-
- Inconsistent naming conventions (use camelCase for functions)
|
|
74
|
+
- Inconsistent naming conventions (use camelCase for functions)
|
|
75
|
+
- Not checking `result.success` before accessing `result.ast`
|
|
76
|
+
- Using input parser for security validation (use `parseWithContext` instead)
|
|
77
|
+
- Forgetting to provide `cursorPosition` when autocomplete is needed
|
|
78
|
+
|
|
79
|
+
## Testing guidelines
|
|
80
|
+
|
|
81
|
+
- All parser features require co-located tests
|
|
82
|
+
- Use divergence tests to document differences between input parser and main parser
|
|
83
|
+
- Token consistency tests verify `parseWithContext` tokens match `parseQueryTokens`
|
|
84
|
+
- Security tests should cover field restrictions, depth limits, and value sanitization
|
package/README.md
CHANGED
|
@@ -275,6 +275,165 @@ const publicSearchKit = createQueryKit({
|
|
|
275
275
|
});
|
|
276
276
|
```
|
|
277
277
|
|
|
278
|
+
## Input Parsing for Search UIs
|
|
279
|
+
|
|
280
|
+
QueryKit provides utilities for building rich search bar experiences with real-time feedback, including key:value highlighting, autocomplete suggestions, and error recovery hints.
|
|
281
|
+
|
|
282
|
+
### Real-Time Token Parsing
|
|
283
|
+
|
|
284
|
+
Use `parseQueryInput` and `parseQueryTokens` for lightweight, real-time parsing as users type:
|
|
285
|
+
|
|
286
|
+
```typescript
|
|
287
|
+
import { parseQueryInput, parseQueryTokens } from '@gblikas/querykit';
|
|
288
|
+
|
|
289
|
+
// Parse input to get terms and cursor context
|
|
290
|
+
const input = 'status:done AND priority:';
|
|
291
|
+
const result = parseQueryInput(input, { cursorPosition: 25 });
|
|
292
|
+
|
|
293
|
+
// result.terms contains parsed terms:
|
|
294
|
+
// [{ key: 'status', value: 'done', ... }, { key: 'priority', value: null, ... }]
|
|
295
|
+
|
|
296
|
+
// result.cursorContext tells you where the cursor is: 'key', 'value', or 'operator'
|
|
297
|
+
console.log(result.cursorContext); // 'value' (cursor is after 'priority:')
|
|
298
|
+
|
|
299
|
+
// Get interleaved tokens (terms + operators) for highlighting
|
|
300
|
+
const tokens = parseQueryTokens(input);
|
|
301
|
+
// [
|
|
302
|
+
// { type: 'term', key: 'status', value: 'done', startPosition: 0, endPosition: 11 },
|
|
303
|
+
// { type: 'operator', operator: 'AND', startPosition: 12, endPosition: 15 },
|
|
304
|
+
// { type: 'term', key: 'priority', value: null, startPosition: 16, endPosition: 25 }
|
|
305
|
+
// ]
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Rich Context with parseWithContext
|
|
309
|
+
|
|
310
|
+
For comprehensive parsing with schema validation, autocomplete, and error recovery:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
import { QueryParser } from '@gblikas/querykit';
|
|
314
|
+
|
|
315
|
+
const parser = new QueryParser();
|
|
316
|
+
|
|
317
|
+
// Define your schema for validation and autocomplete
|
|
318
|
+
const schema = {
|
|
319
|
+
status: {
|
|
320
|
+
type: 'string',
|
|
321
|
+
allowedValues: ['todo', 'doing', 'done'],
|
|
322
|
+
description: 'Task status'
|
|
323
|
+
},
|
|
324
|
+
priority: { type: 'number', description: 'Priority level (1-5)' },
|
|
325
|
+
assignee: { type: 'string', description: 'Assigned user' }
|
|
326
|
+
};
|
|
327
|
+
|
|
328
|
+
const result = parser.parseWithContext('status:do', {
|
|
329
|
+
cursorPosition: 9,
|
|
330
|
+
schema,
|
|
331
|
+
securityOptions: { maxClauseCount: 10 }
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Always returns a result object (never throws)
|
|
335
|
+
console.log(result.success); // true/false - whether parsing succeeded
|
|
336
|
+
console.log(result.tokens); // Tokenized input (always available)
|
|
337
|
+
console.log(result.structure); // Query structure analysis
|
|
338
|
+
console.log(result.ast); // AST (if successful)
|
|
339
|
+
console.log(result.error); // Error details (if failed)
|
|
340
|
+
|
|
341
|
+
// Autocomplete suggestions based on cursor position
|
|
342
|
+
console.log(result.suggestions);
|
|
343
|
+
// {
|
|
344
|
+
// context: 'value',
|
|
345
|
+
// currentField: 'status',
|
|
346
|
+
// values: [
|
|
347
|
+
// { value: 'doing', score: 80 },
|
|
348
|
+
// { value: 'done', score: 80 }
|
|
349
|
+
// ]
|
|
350
|
+
// }
|
|
351
|
+
|
|
352
|
+
// Schema validation results
|
|
353
|
+
console.log(result.fieldValidation);
|
|
354
|
+
// { valid: true, fields: [...], unknownFields: [] }
|
|
355
|
+
|
|
356
|
+
// Security pre-check
|
|
357
|
+
console.log(result.security);
|
|
358
|
+
// { passed: true, violations: [], warnings: [] }
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Error Recovery
|
|
362
|
+
|
|
363
|
+
When parsing fails, `parseWithContext` provides helpful recovery hints:
|
|
364
|
+
|
|
365
|
+
```typescript
|
|
366
|
+
const result = parser.parseWithContext('status:"incomplete');
|
|
367
|
+
|
|
368
|
+
console.log(result.recovery);
|
|
369
|
+
// {
|
|
370
|
+
// issue: 'unclosed_quote',
|
|
371
|
+
// message: 'Unclosed double quote detected',
|
|
372
|
+
// suggestion: 'Add a closing " to complete the quoted value',
|
|
373
|
+
// autofix: 'status:"incomplete"',
|
|
374
|
+
// position: 7
|
|
375
|
+
// }
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
Error types detected:
|
|
379
|
+
- `unclosed_quote` - Missing closing quote (with autofix)
|
|
380
|
+
- `unclosed_parenthesis` - Unbalanced parentheses (with autofix)
|
|
381
|
+
- `trailing_operator` - Query ends with AND/OR/NOT (with autofix)
|
|
382
|
+
- `missing_value` - Field has colon but no value
|
|
383
|
+
- `syntax_error` - Generic syntax issue
|
|
384
|
+
|
|
385
|
+
### Building a Search Bar with Highlighting
|
|
386
|
+
|
|
387
|
+
Here's a React example using the input parser for highlighting:
|
|
388
|
+
|
|
389
|
+
```tsx
|
|
390
|
+
import { parseQueryTokens } from '@gblikas/querykit';
|
|
391
|
+
|
|
392
|
+
function SearchBar({ value, onChange }) {
|
|
393
|
+
const tokens = parseQueryTokens(value);
|
|
394
|
+
|
|
395
|
+
const renderHighlightedQuery = () => {
|
|
396
|
+
if (!value) return null;
|
|
397
|
+
|
|
398
|
+
return tokens.map((token, idx) => {
|
|
399
|
+
const text = value.slice(token.startPosition, token.endPosition);
|
|
400
|
+
|
|
401
|
+
if (token.type === 'operator') {
|
|
402
|
+
return <span key={idx} className="text-purple-500">{text}</span>;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Term token - highlight key and value differently
|
|
406
|
+
if (token.key && token.operator) {
|
|
407
|
+
const keyEnd = token.startPosition + token.key.length;
|
|
408
|
+
const opEnd = keyEnd + token.operator.length;
|
|
409
|
+
return (
|
|
410
|
+
<span key={idx}>
|
|
411
|
+
<span className="text-orange-400">{token.key}</span>
|
|
412
|
+
<span className="text-gray-500">{token.operator}</span>
|
|
413
|
+
<span className="text-blue-400">{value.slice(opEnd, token.endPosition)}</span>
|
|
414
|
+
</span>
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return <span key={idx}>{text}</span>;
|
|
419
|
+
});
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
return (
|
|
423
|
+
<div className="relative">
|
|
424
|
+
<div className="absolute inset-0 pointer-events-none">
|
|
425
|
+
{renderHighlightedQuery()}
|
|
426
|
+
</div>
|
|
427
|
+
<input
|
|
428
|
+
value={value}
|
|
429
|
+
onChange={(e) => onChange(e.target.value)}
|
|
430
|
+
className="bg-transparent text-transparent caret-black"
|
|
431
|
+
/>
|
|
432
|
+
</div>
|
|
433
|
+
);
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
278
437
|
## Roadmap
|
|
279
438
|
|
|
280
439
|
### Core Parsing Engine and DSL
|
|
@@ -283,6 +442,9 @@ const publicSearchKit = createQueryKit({
|
|
|
283
442
|
- [x] Develop internal AST representation
|
|
284
443
|
- [x] Implement consistent syntax for logical operators (AND, OR, NOT)
|
|
285
444
|
- [x] Support standard comparison operators (==, !=, >, >=, <, <=)
|
|
445
|
+
- [x] Real-time input parsing for search UIs
|
|
446
|
+
- [x] Autocomplete suggestions with schema awareness
|
|
447
|
+
- [x] Error recovery hints with autofix
|
|
286
448
|
|
|
287
449
|
### First Adapters
|
|
288
450
|
- [x] Drizzle ORM integration
|
|
@@ -299,7 +461,7 @@ const publicSearchKit = createQueryKit({
|
|
|
299
461
|
- [ ] Pagination helpers
|
|
300
462
|
|
|
301
463
|
### Ecosystem Expansion
|
|
302
|
-
- [
|
|
464
|
+
- [x] Frontend query builder components (input parser)
|
|
303
465
|
- [ ] Additional ORM adapters
|
|
304
466
|
- [ ] Server middleware for Express/Fastify
|
|
305
467
|
- [ ] TypeScript SDK generation
|
package/dist/parser/index.d.ts
CHANGED
package/dist/parser/index.js
CHANGED
|
@@ -16,3 +16,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./types"), exports);
|
|
18
18
|
__exportStar(require("./parser"), exports);
|
|
19
|
+
__exportStar(require("./input-parser"), exports);
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Input Parser for QueryKit
|
|
3
|
+
*
|
|
4
|
+
* This module provides utilities for parsing partial/in-progress query input
|
|
5
|
+
* from search bars, enabling features like:
|
|
6
|
+
* - Key-value highlighting
|
|
7
|
+
* - Autocomplete suggestions
|
|
8
|
+
* - Real-time validation feedback
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Represents the context of where the cursor is within a query term
|
|
12
|
+
*/
|
|
13
|
+
export type CursorContext = 'key' | 'operator' | 'value' | 'empty' | 'between';
|
|
14
|
+
/**
|
|
15
|
+
* Represents the parsed context of a single query term
|
|
16
|
+
*/
|
|
17
|
+
export interface IQueryInputTerm {
|
|
18
|
+
/**
|
|
19
|
+
* The field/key being typed (e.g., "status" in "status:done")
|
|
20
|
+
* Will be null if only a bare value is being typed
|
|
21
|
+
*/
|
|
22
|
+
key: string | null;
|
|
23
|
+
/**
|
|
24
|
+
* The operator being used (e.g., ":", ">", ">=", "<", "<=", "!=")
|
|
25
|
+
* Will be null if no operator has been typed yet
|
|
26
|
+
*/
|
|
27
|
+
operator: string | null;
|
|
28
|
+
/**
|
|
29
|
+
* The value being typed (e.g., "done" in "status:done")
|
|
30
|
+
* Will be null if no value has been typed yet
|
|
31
|
+
*/
|
|
32
|
+
value: string | null;
|
|
33
|
+
/**
|
|
34
|
+
* The start position of this term in the original input string
|
|
35
|
+
*/
|
|
36
|
+
startPosition: number;
|
|
37
|
+
/**
|
|
38
|
+
* The end position of this term in the original input string
|
|
39
|
+
*/
|
|
40
|
+
endPosition: number;
|
|
41
|
+
/**
|
|
42
|
+
* The original raw text of this term
|
|
43
|
+
*/
|
|
44
|
+
raw: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Represents the result of parsing query input
|
|
48
|
+
*/
|
|
49
|
+
export interface IQueryInputContext {
|
|
50
|
+
/**
|
|
51
|
+
* All terms found in the input
|
|
52
|
+
*/
|
|
53
|
+
terms: IQueryInputTerm[];
|
|
54
|
+
/**
|
|
55
|
+
* The term where the cursor is currently positioned (if cursorPosition was provided)
|
|
56
|
+
* Will be null if cursor is not within any term
|
|
57
|
+
*/
|
|
58
|
+
activeTerm: IQueryInputTerm | null;
|
|
59
|
+
/**
|
|
60
|
+
* Where the cursor is within the active term
|
|
61
|
+
*/
|
|
62
|
+
cursorContext: CursorContext;
|
|
63
|
+
/**
|
|
64
|
+
* The original input string
|
|
65
|
+
*/
|
|
66
|
+
input: string;
|
|
67
|
+
/**
|
|
68
|
+
* The cursor position (if provided)
|
|
69
|
+
*/
|
|
70
|
+
cursorPosition: number | null;
|
|
71
|
+
/**
|
|
72
|
+
* Logical operators found between terms (AND, OR, NOT)
|
|
73
|
+
*/
|
|
74
|
+
logicalOperators: Array<{
|
|
75
|
+
operator: string;
|
|
76
|
+
position: number;
|
|
77
|
+
}>;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Options for parsing query input
|
|
81
|
+
*/
|
|
82
|
+
export interface IQueryInputParserOptions {
|
|
83
|
+
/**
|
|
84
|
+
* Whether to treat the input as case-insensitive for keys
|
|
85
|
+
* @default false
|
|
86
|
+
*/
|
|
87
|
+
caseInsensitiveKeys?: boolean;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Parse query input to extract structured information about the current search state.
|
|
91
|
+
*
|
|
92
|
+
* This function is designed for real-time parsing of user input in a search bar,
|
|
93
|
+
* allowing developers to:
|
|
94
|
+
* - Highlight keys and values differently
|
|
95
|
+
* - Provide autocomplete suggestions based on context
|
|
96
|
+
* - Validate input as the user types
|
|
97
|
+
*
|
|
98
|
+
* @param input The current search input string
|
|
99
|
+
* @param cursorPosition Optional cursor position to determine the active term
|
|
100
|
+
* @param options Optional parsing options
|
|
101
|
+
* @returns Structured information about the query input
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* // User is typing "status:d" (intending to type "status:done")
|
|
106
|
+
* const result = parseQueryInput('status:d');
|
|
107
|
+
* // result.terms[0] = { key: 'status', operator: ':', value: 'd', ... }
|
|
108
|
+
* // result.activeTerm = { key: 'status', operator: ':', value: 'd', ... }
|
|
109
|
+
* // result.cursorContext = 'value'
|
|
110
|
+
*
|
|
111
|
+
* // User is typing "priority:>2 status:"
|
|
112
|
+
* const result = parseQueryInput('priority:>2 status:', 19);
|
|
113
|
+
* // result.terms[0] = { key: 'priority', operator: ':>', value: '2', ... }
|
|
114
|
+
* // result.terms[1] = { key: 'status', operator: ':', value: null, ... }
|
|
115
|
+
* // result.activeTerm = result.terms[1] (cursor is at position 19)
|
|
116
|
+
* // result.cursorContext = 'value' (waiting for value input)
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
export declare function parseQueryInput(input: string, cursorPosition?: number, options?: IQueryInputParserOptions): IQueryInputContext;
|
|
120
|
+
/**
|
|
121
|
+
* Get the term at a specific cursor position.
|
|
122
|
+
* Convenience function for quick lookups.
|
|
123
|
+
*
|
|
124
|
+
* @param input The query input string
|
|
125
|
+
* @param cursorPosition The cursor position
|
|
126
|
+
* @returns The term at the cursor position, or null if none
|
|
127
|
+
*/
|
|
128
|
+
export declare function getTermAtPosition(input: string, cursorPosition: number): IQueryInputTerm | null;
|
|
129
|
+
/**
|
|
130
|
+
* Check if the input appears to be a complete, valid query expression.
|
|
131
|
+
* This is a lightweight check - it doesn't guarantee the query will parse successfully.
|
|
132
|
+
*
|
|
133
|
+
* @param input The query input string
|
|
134
|
+
* @returns true if the input appears complete, false if it looks incomplete
|
|
135
|
+
*/
|
|
136
|
+
export declare function isInputComplete(input: string): boolean;
|
|
137
|
+
/**
|
|
138
|
+
* Extract just the key and value from a simple input.
|
|
139
|
+
* Convenience function for the most common use case.
|
|
140
|
+
*
|
|
141
|
+
* @param input The query input string (e.g., "status:done")
|
|
142
|
+
* @returns Object with key and value, or null if not a key:value pattern
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* extractKeyValue('status:done');
|
|
147
|
+
* // { key: 'status', value: 'done' }
|
|
148
|
+
*
|
|
149
|
+
* extractKeyValue('status:');
|
|
150
|
+
* // { key: 'status', value: null }
|
|
151
|
+
*
|
|
152
|
+
* extractKeyValue('hello');
|
|
153
|
+
* // null (no key:value pattern)
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
export declare function extractKeyValue(input: string): {
|
|
157
|
+
key: string;
|
|
158
|
+
value: string | null;
|
|
159
|
+
} | null;
|
|
160
|
+
import type { IQueryToken } from './types';
|
|
161
|
+
/**
|
|
162
|
+
* A token in the query sequence - either a term or a logical operator
|
|
163
|
+
* This is an alias for IQueryToken from types.ts
|
|
164
|
+
*/
|
|
165
|
+
export type QueryToken = IQueryToken;
|
|
166
|
+
/**
|
|
167
|
+
* Result of parsing query input into an interleaved token sequence
|
|
168
|
+
*/
|
|
169
|
+
export interface IQueryTokenSequence {
|
|
170
|
+
/**
|
|
171
|
+
* Ordered sequence of tokens (terms and operators interleaved)
|
|
172
|
+
*/
|
|
173
|
+
tokens: QueryToken[];
|
|
174
|
+
/**
|
|
175
|
+
* The original input string
|
|
176
|
+
*/
|
|
177
|
+
input: string;
|
|
178
|
+
/**
|
|
179
|
+
* The token where the cursor is currently positioned (if cursorPosition was provided)
|
|
180
|
+
*/
|
|
181
|
+
activeToken: QueryToken | null;
|
|
182
|
+
/**
|
|
183
|
+
* Index of the active token in the tokens array (-1 if none)
|
|
184
|
+
*/
|
|
185
|
+
activeTokenIndex: number;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Parse query input into an interleaved sequence of terms and operators.
|
|
189
|
+
*
|
|
190
|
+
* This provides a flat, ordered representation ideal for:
|
|
191
|
+
* - Rendering query tokens as UI chips/tags
|
|
192
|
+
* - Building visual query builders
|
|
193
|
+
* - Syntax highlighting with proper ordering
|
|
194
|
+
*
|
|
195
|
+
* @param input The query input string
|
|
196
|
+
* @param cursorPosition Optional cursor position to identify active token
|
|
197
|
+
* @returns Ordered sequence of term and operator tokens
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```typescript
|
|
201
|
+
* const result = parseQueryTokens('status:done AND priority:high');
|
|
202
|
+
* // result.tokens = [
|
|
203
|
+
* // { type: 'term', key: 'status', value: 'done', ... },
|
|
204
|
+
* // { type: 'operator', operator: 'AND', ... },
|
|
205
|
+
* // { type: 'term', key: 'priority', value: 'high', ... }
|
|
206
|
+
* // ]
|
|
207
|
+
*
|
|
208
|
+
* // For incomplete input like 'status:d'
|
|
209
|
+
* const result = parseQueryTokens('status:d');
|
|
210
|
+
* // result.tokens = [
|
|
211
|
+
* // { type: 'term', key: 'status', value: 'd', ... }
|
|
212
|
+
* // ]
|
|
213
|
+
* ```
|
|
214
|
+
*/
|
|
215
|
+
export declare function parseQueryTokens(input: string, cursorPosition?: number): IQueryTokenSequence;
|