@standardbeagle/dart-query 0.3.2

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 (128) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +427 -0
  3. package/TOOLS.md +2148 -0
  4. package/dist/api/dartClient.d.ts +123 -0
  5. package/dist/api/dartClient.d.ts.map +1 -0
  6. package/dist/api/dartClient.js +436 -0
  7. package/dist/api/dartClient.js.map +1 -0
  8. package/dist/batch/batchOperations.d.ts +14 -0
  9. package/dist/batch/batchOperations.d.ts.map +1 -0
  10. package/dist/batch/batchOperations.js +65 -0
  11. package/dist/batch/batchOperations.js.map +1 -0
  12. package/dist/cache/configCache.d.ts +20 -0
  13. package/dist/cache/configCache.d.ts.map +1 -0
  14. package/dist/cache/configCache.js +59 -0
  15. package/dist/cache/configCache.js.map +1 -0
  16. package/dist/index.d.ts +3 -0
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +1120 -0
  19. package/dist/index.js.map +1 -0
  20. package/dist/parsers/csv.d.ts +44 -0
  21. package/dist/parsers/csv.d.ts.map +1 -0
  22. package/dist/parsers/csv.js +574 -0
  23. package/dist/parsers/csv.js.map +1 -0
  24. package/dist/parsers/dartql.d.ts +104 -0
  25. package/dist/parsers/dartql.d.ts.map +1 -0
  26. package/dist/parsers/dartql.js +889 -0
  27. package/dist/parsers/dartql.js.map +1 -0
  28. package/dist/tools/add_task_comment.d.ts +3 -0
  29. package/dist/tools/add_task_comment.d.ts.map +1 -0
  30. package/dist/tools/add_task_comment.js +43 -0
  31. package/dist/tools/add_task_comment.js.map +1 -0
  32. package/dist/tools/add_time_tracking.d.ts +3 -0
  33. package/dist/tools/add_time_tracking.d.ts.map +1 -0
  34. package/dist/tools/add_time_tracking.js +52 -0
  35. package/dist/tools/add_time_tracking.js.map +1 -0
  36. package/dist/tools/attach_url.d.ts +3 -0
  37. package/dist/tools/attach_url.d.ts.map +1 -0
  38. package/dist/tools/attach_url.js +38 -0
  39. package/dist/tools/attach_url.js.map +1 -0
  40. package/dist/tools/batch_delete_tasks.d.ts +3 -0
  41. package/dist/tools/batch_delete_tasks.d.ts.map +1 -0
  42. package/dist/tools/batch_delete_tasks.js +125 -0
  43. package/dist/tools/batch_delete_tasks.js.map +1 -0
  44. package/dist/tools/batch_update_tasks.d.ts +3 -0
  45. package/dist/tools/batch_update_tasks.d.ts.map +1 -0
  46. package/dist/tools/batch_update_tasks.js +327 -0
  47. package/dist/tools/batch_update_tasks.js.map +1 -0
  48. package/dist/tools/create_doc.d.ts +3 -0
  49. package/dist/tools/create_doc.d.ts.map +1 -0
  50. package/dist/tools/create_doc.js +65 -0
  51. package/dist/tools/create_doc.js.map +1 -0
  52. package/dist/tools/create_task.d.ts +3 -0
  53. package/dist/tools/create_task.d.ts.map +1 -0
  54. package/dist/tools/create_task.js +143 -0
  55. package/dist/tools/create_task.js.map +1 -0
  56. package/dist/tools/delete_doc.d.ts +3 -0
  57. package/dist/tools/delete_doc.d.ts.map +1 -0
  58. package/dist/tools/delete_doc.js +35 -0
  59. package/dist/tools/delete_doc.js.map +1 -0
  60. package/dist/tools/delete_task.d.ts +3 -0
  61. package/dist/tools/delete_task.d.ts.map +1 -0
  62. package/dist/tools/delete_task.js +35 -0
  63. package/dist/tools/delete_task.js.map +1 -0
  64. package/dist/tools/get_batch_status.d.ts +3 -0
  65. package/dist/tools/get_batch_status.d.ts.map +1 -0
  66. package/dist/tools/get_batch_status.js +24 -0
  67. package/dist/tools/get_batch_status.js.map +1 -0
  68. package/dist/tools/get_config.d.ts +3 -0
  69. package/dist/tools/get_config.d.ts.map +1 -0
  70. package/dist/tools/get_config.js +74 -0
  71. package/dist/tools/get_config.js.map +1 -0
  72. package/dist/tools/get_dartboard.d.ts +3 -0
  73. package/dist/tools/get_dartboard.d.ts.map +1 -0
  74. package/dist/tools/get_dartboard.js +43 -0
  75. package/dist/tools/get_dartboard.js.map +1 -0
  76. package/dist/tools/get_doc.d.ts +3 -0
  77. package/dist/tools/get_doc.d.ts.map +1 -0
  78. package/dist/tools/get_doc.js +34 -0
  79. package/dist/tools/get_doc.js.map +1 -0
  80. package/dist/tools/get_folder.d.ts +3 -0
  81. package/dist/tools/get_folder.d.ts.map +1 -0
  82. package/dist/tools/get_folder.js +45 -0
  83. package/dist/tools/get_folder.js.map +1 -0
  84. package/dist/tools/get_task.d.ts +3 -0
  85. package/dist/tools/get_task.d.ts.map +1 -0
  86. package/dist/tools/get_task.js +109 -0
  87. package/dist/tools/get_task.js.map +1 -0
  88. package/dist/tools/import_tasks_csv.d.ts +3 -0
  89. package/dist/tools/import_tasks_csv.d.ts.map +1 -0
  90. package/dist/tools/import_tasks_csv.js +218 -0
  91. package/dist/tools/import_tasks_csv.js.map +1 -0
  92. package/dist/tools/info.d.ts +3 -0
  93. package/dist/tools/info.d.ts.map +1 -0
  94. package/dist/tools/info.js +474 -0
  95. package/dist/tools/info.js.map +1 -0
  96. package/dist/tools/list_comments.d.ts +3 -0
  97. package/dist/tools/list_comments.d.ts.map +1 -0
  98. package/dist/tools/list_comments.js +46 -0
  99. package/dist/tools/list_comments.js.map +1 -0
  100. package/dist/tools/list_docs.d.ts +3 -0
  101. package/dist/tools/list_docs.d.ts.map +1 -0
  102. package/dist/tools/list_docs.js +101 -0
  103. package/dist/tools/list_docs.js.map +1 -0
  104. package/dist/tools/list_tasks.d.ts +3 -0
  105. package/dist/tools/list_tasks.d.ts.map +1 -0
  106. package/dist/tools/list_tasks.js +325 -0
  107. package/dist/tools/list_tasks.js.map +1 -0
  108. package/dist/tools/move_task.d.ts +3 -0
  109. package/dist/tools/move_task.d.ts.map +1 -0
  110. package/dist/tools/move_task.js +44 -0
  111. package/dist/tools/move_task.js.map +1 -0
  112. package/dist/tools/search_tasks.d.ts +3 -0
  113. package/dist/tools/search_tasks.d.ts.map +1 -0
  114. package/dist/tools/search_tasks.js +227 -0
  115. package/dist/tools/search_tasks.js.map +1 -0
  116. package/dist/tools/update_doc.d.ts +3 -0
  117. package/dist/tools/update_doc.d.ts.map +1 -0
  118. package/dist/tools/update_doc.js +102 -0
  119. package/dist/tools/update_doc.js.map +1 -0
  120. package/dist/tools/update_task.d.ts +3 -0
  121. package/dist/tools/update_task.d.ts.map +1 -0
  122. package/dist/tools/update_task.js +241 -0
  123. package/dist/tools/update_task.js.map +1 -0
  124. package/dist/types/index.d.ts +529 -0
  125. package/dist/types/index.d.ts.map +1 -0
  126. package/dist/types/index.js +65 -0
  127. package/dist/types/index.js.map +1 -0
  128. package/package.json +72 -0
@@ -0,0 +1,889 @@
1
+ import { DartQLParseError } from '../types/index.js';
2
+ export var TokenType;
3
+ (function (TokenType) {
4
+ TokenType["IDENTIFIER"] = "IDENTIFIER";
5
+ TokenType["STRING"] = "STRING";
6
+ TokenType["NUMBER"] = "NUMBER";
7
+ TokenType["EQUALS"] = "EQUALS";
8
+ TokenType["NOT_EQUALS"] = "NOT_EQUALS";
9
+ TokenType["GREATER_THAN"] = "GREATER_THAN";
10
+ TokenType["GREATER_EQUAL"] = "GREATER_EQUAL";
11
+ TokenType["LESS_THAN"] = "LESS_THAN";
12
+ TokenType["LESS_EQUAL"] = "LESS_EQUAL";
13
+ TokenType["AND"] = "AND";
14
+ TokenType["OR"] = "OR";
15
+ TokenType["NOT"] = "NOT";
16
+ TokenType["IN"] = "IN";
17
+ TokenType["LIKE"] = "LIKE";
18
+ TokenType["CONTAINS"] = "CONTAINS";
19
+ TokenType["IS"] = "IS";
20
+ TokenType["NULL"] = "NULL";
21
+ TokenType["BETWEEN"] = "BETWEEN";
22
+ TokenType["LPAREN"] = "LPAREN";
23
+ TokenType["RPAREN"] = "RPAREN";
24
+ TokenType["COMMA"] = "COMMA";
25
+ TokenType["EOF"] = "EOF";
26
+ TokenType["UNKNOWN"] = "UNKNOWN";
27
+ })(TokenType || (TokenType = {}));
28
+ export const VALID_FIELDS = [
29
+ 'status',
30
+ 'priority',
31
+ 'size',
32
+ 'title',
33
+ 'description',
34
+ 'assignee',
35
+ 'dartboard',
36
+ 'tags',
37
+ 'created_at',
38
+ 'updated_at',
39
+ 'due_at',
40
+ 'start_at',
41
+ 'completed_at',
42
+ 'parent_task',
43
+ 'dart_id',
44
+ 'subtask_ids',
45
+ 'blocker_ids',
46
+ 'blocking_ids',
47
+ 'duplicate_ids',
48
+ 'related_ids',
49
+ ];
50
+ export class DartQLTokenizer {
51
+ input;
52
+ position;
53
+ tokens;
54
+ constructor(input) {
55
+ this.input = input.trim();
56
+ this.position = 0;
57
+ this.tokens = [];
58
+ }
59
+ tokenize() {
60
+ this.tokens = [];
61
+ this.position = 0;
62
+ while (this.position < this.input.length) {
63
+ this.skipWhitespace();
64
+ if (this.position >= this.input.length) {
65
+ break;
66
+ }
67
+ const token = this.nextToken();
68
+ if (token) {
69
+ this.tokens.push(token);
70
+ }
71
+ }
72
+ this.tokens.push({
73
+ type: TokenType.EOF,
74
+ value: '',
75
+ position: this.position,
76
+ length: 0,
77
+ });
78
+ return this.tokens;
79
+ }
80
+ skipWhitespace() {
81
+ while (this.position < this.input.length && /\s/.test(this.input[this.position])) {
82
+ this.position++;
83
+ }
84
+ }
85
+ peek(offset = 0) {
86
+ const pos = this.position + offset;
87
+ return pos < this.input.length ? this.input[pos] : '';
88
+ }
89
+ consume() {
90
+ if (this.position >= this.input.length) {
91
+ return '';
92
+ }
93
+ return this.input[this.position++];
94
+ }
95
+ isAtEnd() {
96
+ return this.position >= this.input.length;
97
+ }
98
+ nextToken() {
99
+ const start = this.position;
100
+ const char = this.peek();
101
+ if (char === '"' || char === "'") {
102
+ return this.readString();
103
+ }
104
+ if (/\d/.test(char)) {
105
+ return this.readNumber();
106
+ }
107
+ if (char === '=' || char === '!' || char === '>' || char === '<') {
108
+ return this.readOperator();
109
+ }
110
+ if (char === '(') {
111
+ this.consume();
112
+ return { type: TokenType.LPAREN, value: '(', position: start, length: 1 };
113
+ }
114
+ if (char === ')') {
115
+ this.consume();
116
+ return { type: TokenType.RPAREN, value: ')', position: start, length: 1 };
117
+ }
118
+ if (char === ',') {
119
+ this.consume();
120
+ return { type: TokenType.COMMA, value: ',', position: start, length: 1 };
121
+ }
122
+ if (/[a-zA-Z_]/.test(char)) {
123
+ return this.readIdentifierOrKeyword();
124
+ }
125
+ throw new DartQLParseError(`Unexpected character: '${char}'`, this.position, char);
126
+ }
127
+ readString() {
128
+ const start = this.position;
129
+ const quote = this.consume();
130
+ let value = '';
131
+ while (!this.isAtEnd() && this.peek() !== quote) {
132
+ const char = this.consume();
133
+ if (char === '\\' && !this.isAtEnd()) {
134
+ const next = this.consume();
135
+ switch (next) {
136
+ case 'n':
137
+ value += '\n';
138
+ break;
139
+ case 't':
140
+ value += '\t';
141
+ break;
142
+ case 'r':
143
+ value += '\r';
144
+ break;
145
+ case '\\':
146
+ value += '\\';
147
+ break;
148
+ case quote:
149
+ value += quote;
150
+ break;
151
+ default: value += next;
152
+ }
153
+ }
154
+ else {
155
+ value += char;
156
+ }
157
+ }
158
+ if (this.isAtEnd()) {
159
+ throw new DartQLParseError(`Unterminated string literal starting at position ${start}`, start, quote);
160
+ }
161
+ this.consume();
162
+ const length = this.position - start;
163
+ return {
164
+ type: TokenType.STRING,
165
+ value,
166
+ position: start,
167
+ length,
168
+ };
169
+ }
170
+ readNumber() {
171
+ const start = this.position;
172
+ let value = '';
173
+ while (!this.isAtEnd() && /\d/.test(this.peek())) {
174
+ value += this.consume();
175
+ }
176
+ if (this.peek() === '.' && /\d/.test(this.peek(1))) {
177
+ value += this.consume();
178
+ while (!this.isAtEnd() && /\d/.test(this.peek())) {
179
+ value += this.consume();
180
+ }
181
+ }
182
+ const length = this.position - start;
183
+ return {
184
+ type: TokenType.NUMBER,
185
+ value,
186
+ position: start,
187
+ length,
188
+ };
189
+ }
190
+ readOperator() {
191
+ const start = this.position;
192
+ const first = this.consume();
193
+ const second = this.peek();
194
+ if (first === '!' && second === '=') {
195
+ this.consume();
196
+ return { type: TokenType.NOT_EQUALS, value: '!=', position: start, length: 2 };
197
+ }
198
+ if (first === '>' && second === '=') {
199
+ this.consume();
200
+ return { type: TokenType.GREATER_EQUAL, value: '>=', position: start, length: 2 };
201
+ }
202
+ if (first === '<' && second === '=') {
203
+ this.consume();
204
+ return { type: TokenType.LESS_EQUAL, value: '<=', position: start, length: 2 };
205
+ }
206
+ if (first === '=') {
207
+ return { type: TokenType.EQUALS, value: '=', position: start, length: 1 };
208
+ }
209
+ if (first === '>') {
210
+ return { type: TokenType.GREATER_THAN, value: '>', position: start, length: 1 };
211
+ }
212
+ if (first === '<') {
213
+ return { type: TokenType.LESS_THAN, value: '<', position: start, length: 1 };
214
+ }
215
+ throw new DartQLParseError(`Invalid operator starting with '${first}'`, start, first);
216
+ }
217
+ readIdentifierOrKeyword() {
218
+ const start = this.position;
219
+ let value = '';
220
+ while (!this.isAtEnd() && /[a-zA-Z0-9_]/.test(this.peek())) {
221
+ value += this.consume();
222
+ }
223
+ const length = this.position - start;
224
+ const upperValue = value.toUpperCase();
225
+ const keywordMap = {
226
+ 'AND': TokenType.AND,
227
+ 'OR': TokenType.OR,
228
+ 'NOT': TokenType.NOT,
229
+ 'IN': TokenType.IN,
230
+ 'LIKE': TokenType.LIKE,
231
+ 'CONTAINS': TokenType.CONTAINS,
232
+ 'IS': TokenType.IS,
233
+ 'NULL': TokenType.NULL,
234
+ 'BETWEEN': TokenType.BETWEEN,
235
+ };
236
+ const type = keywordMap[upperValue] || TokenType.IDENTIFIER;
237
+ return {
238
+ type,
239
+ value,
240
+ position: start,
241
+ length,
242
+ };
243
+ }
244
+ }
245
+ export class DartQLLexer {
246
+ tokens;
247
+ errors;
248
+ fields;
249
+ constructor(tokens) {
250
+ this.tokens = tokens;
251
+ this.errors = [];
252
+ this.fields = new Set();
253
+ }
254
+ analyze() {
255
+ this.errors = [];
256
+ this.fields = new Set();
257
+ for (let i = 0; i < this.tokens.length; i++) {
258
+ const token = this.tokens[i];
259
+ if (token.type === TokenType.IDENTIFIER) {
260
+ this.validateFieldName(token);
261
+ }
262
+ if (token.type === TokenType.NOT) {
263
+ const next = this.tokens[i + 1];
264
+ if (next && next.type === TokenType.IN) {
265
+ continue;
266
+ }
267
+ else if (next && next.type === TokenType.LPAREN) {
268
+ continue;
269
+ }
270
+ }
271
+ if (token.type === TokenType.IS) {
272
+ const next = this.tokens[i + 1];
273
+ if (!next || (next.type !== TokenType.NULL && next.type !== TokenType.NOT)) {
274
+ this.errors.push(`IS keyword must be followed by NULL or NOT NULL at position ${token.position}`);
275
+ }
276
+ }
277
+ }
278
+ return {
279
+ tokens: this.tokens,
280
+ errors: this.errors,
281
+ fields: Array.from(this.fields),
282
+ };
283
+ }
284
+ validateFieldName(token) {
285
+ const fieldName = token.value.toLowerCase();
286
+ this.fields.add(fieldName);
287
+ if (!VALID_FIELDS.includes(fieldName)) {
288
+ const suggestion = this.findClosestField(fieldName);
289
+ if (suggestion) {
290
+ this.errors.push(`Unknown field: '${token.value}'. Did you mean '${suggestion}'? (at position ${token.position})`);
291
+ }
292
+ else {
293
+ this.errors.push(`Unknown field: '${token.value}'. Valid fields: ${VALID_FIELDS.join(', ')} (at position ${token.position})`);
294
+ }
295
+ }
296
+ }
297
+ findClosestField(input) {
298
+ const threshold = 2;
299
+ let closest = null;
300
+ let minDistance = Infinity;
301
+ for (const field of VALID_FIELDS) {
302
+ const distance = this.levenshteinDistance(input, field);
303
+ if (distance < minDistance && distance <= threshold) {
304
+ minDistance = distance;
305
+ closest = field;
306
+ }
307
+ }
308
+ return closest;
309
+ }
310
+ levenshteinDistance(a, b) {
311
+ const matrix = [];
312
+ for (let i = 0; i <= b.length; i++) {
313
+ matrix[i] = [i];
314
+ }
315
+ for (let j = 0; j <= a.length; j++) {
316
+ matrix[0][j] = j;
317
+ }
318
+ for (let i = 1; i <= b.length; i++) {
319
+ for (let j = 1; j <= a.length; j++) {
320
+ if (b.charAt(i - 1) === a.charAt(j - 1)) {
321
+ matrix[i][j] = matrix[i - 1][j - 1];
322
+ }
323
+ else {
324
+ matrix[i][j] = Math.min(matrix[i - 1][j - 1] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j] + 1);
325
+ }
326
+ }
327
+ }
328
+ return matrix[b.length][a.length];
329
+ }
330
+ }
331
+ export class DartQLParser {
332
+ tokens;
333
+ position;
334
+ errors;
335
+ fields;
336
+ constructor(tokens) {
337
+ this.tokens = tokens;
338
+ this.position = 0;
339
+ this.errors = [];
340
+ this.fields = new Set();
341
+ }
342
+ parse() {
343
+ this.position = 0;
344
+ this.errors = [];
345
+ this.fields = new Set();
346
+ if (this.tokens.length === 1 && this.tokens[0].type === TokenType.EOF) {
347
+ this.errors.push('Empty query - no expression to parse');
348
+ return {
349
+ ast: { type: 'group', expressions: [] },
350
+ fields: [],
351
+ errors: this.errors,
352
+ };
353
+ }
354
+ try {
355
+ const ast = this.parseExpression();
356
+ if (this.current().type !== TokenType.EOF) {
357
+ this.addError(`Unexpected token: '${this.current().value}' at position ${this.current().position}`);
358
+ }
359
+ return {
360
+ ast,
361
+ fields: Array.from(this.fields),
362
+ errors: this.errors,
363
+ };
364
+ }
365
+ catch (error) {
366
+ if (error instanceof DartQLParseError) {
367
+ this.errors.push(error.message);
368
+ }
369
+ else {
370
+ this.errors.push(`Parse error: ${error instanceof Error ? error.message : String(error)}`);
371
+ }
372
+ return {
373
+ ast: { type: 'group', expressions: [] },
374
+ fields: Array.from(this.fields),
375
+ errors: this.errors,
376
+ };
377
+ }
378
+ }
379
+ current() {
380
+ return this.tokens[this.position] || this.tokens[this.tokens.length - 1];
381
+ }
382
+ peek(offset = 1) {
383
+ const pos = this.position + offset;
384
+ return this.tokens[pos] || this.tokens[this.tokens.length - 1];
385
+ }
386
+ consume() {
387
+ const token = this.current();
388
+ if (token.type !== TokenType.EOF) {
389
+ this.position++;
390
+ }
391
+ return token;
392
+ }
393
+ match(...types) {
394
+ return types.includes(this.current().type);
395
+ }
396
+ expect(type, errorMessage) {
397
+ if (!this.match(type)) {
398
+ this.addError(errorMessage + ` at position ${this.current().position}`);
399
+ throw new DartQLParseError(errorMessage, this.current().position, this.current().value);
400
+ }
401
+ return this.consume();
402
+ }
403
+ addError(message) {
404
+ this.errors.push(message);
405
+ }
406
+ parseExpression() {
407
+ let left = this.parseAndExpression();
408
+ while (this.match(TokenType.OR)) {
409
+ this.consume();
410
+ const right = this.parseAndExpression();
411
+ left = {
412
+ type: 'logical',
413
+ operator: 'OR',
414
+ left,
415
+ right,
416
+ };
417
+ }
418
+ return left;
419
+ }
420
+ parseAndExpression() {
421
+ let left = this.parseNotExpression();
422
+ while (this.match(TokenType.AND)) {
423
+ this.consume();
424
+ const right = this.parseNotExpression();
425
+ left = {
426
+ type: 'logical',
427
+ operator: 'AND',
428
+ left,
429
+ right,
430
+ };
431
+ }
432
+ return left;
433
+ }
434
+ parseNotExpression() {
435
+ if (this.match(TokenType.NOT)) {
436
+ this.consume();
437
+ if (this.match(TokenType.IN)) {
438
+ this.position--;
439
+ return this.parsePrimary();
440
+ }
441
+ const expression = this.parseNotExpression();
442
+ return {
443
+ type: 'logical',
444
+ operator: 'NOT',
445
+ right: expression,
446
+ };
447
+ }
448
+ return this.parsePrimary();
449
+ }
450
+ parsePrimary() {
451
+ if (this.match(TokenType.LPAREN)) {
452
+ this.consume();
453
+ const expression = this.parseExpression();
454
+ this.expect(TokenType.RPAREN, 'Expected closing parenthesis');
455
+ return {
456
+ type: 'group',
457
+ expressions: [expression],
458
+ };
459
+ }
460
+ return this.parseComparison();
461
+ }
462
+ parseComparison() {
463
+ const fieldToken = this.expect(TokenType.IDENTIFIER, 'Expected field name');
464
+ const field = fieldToken.value.toLowerCase();
465
+ this.fields.add(field);
466
+ if (this.match(TokenType.IS)) {
467
+ this.consume();
468
+ const isNot = this.match(TokenType.NOT);
469
+ if (isNot) {
470
+ this.consume();
471
+ }
472
+ this.expect(TokenType.NULL, 'Expected NULL after IS or IS NOT');
473
+ return {
474
+ type: 'comparison',
475
+ field,
476
+ operator: isNot ? 'IS NOT NULL' : 'IS NULL',
477
+ value: null,
478
+ };
479
+ }
480
+ if (this.match(TokenType.NOT)) {
481
+ const next = this.peek();
482
+ if (next.type === TokenType.IN) {
483
+ this.consume();
484
+ this.consume();
485
+ const value = this.parseInArray();
486
+ return {
487
+ type: 'comparison',
488
+ field,
489
+ operator: 'NOT IN',
490
+ value,
491
+ };
492
+ }
493
+ }
494
+ if (this.match(TokenType.IN)) {
495
+ this.consume();
496
+ const value = this.parseInArray();
497
+ return {
498
+ type: 'comparison',
499
+ field,
500
+ operator: 'IN',
501
+ value,
502
+ };
503
+ }
504
+ if (this.match(TokenType.BETWEEN)) {
505
+ this.consume();
506
+ const start = this.parseValue();
507
+ this.expect(TokenType.AND, 'Expected AND in BETWEEN clause');
508
+ const end = this.parseValue();
509
+ return {
510
+ type: 'comparison',
511
+ field,
512
+ operator: 'BETWEEN',
513
+ value: [start, end],
514
+ };
515
+ }
516
+ const operator = this.parseOperator();
517
+ const value = this.parseValue();
518
+ return {
519
+ type: 'comparison',
520
+ field,
521
+ operator,
522
+ value,
523
+ };
524
+ }
525
+ parseOperator() {
526
+ const token = this.current();
527
+ const operatorMap = {
528
+ [TokenType.EQUALS]: '=',
529
+ [TokenType.NOT_EQUALS]: '!=',
530
+ [TokenType.GREATER_THAN]: '>',
531
+ [TokenType.GREATER_EQUAL]: '>=',
532
+ [TokenType.LESS_THAN]: '<',
533
+ [TokenType.LESS_EQUAL]: '<=',
534
+ [TokenType.LIKE]: 'LIKE',
535
+ [TokenType.CONTAINS]: 'CONTAINS',
536
+ };
537
+ const operator = operatorMap[token.type];
538
+ if (!operator) {
539
+ this.addError(`Expected comparison operator, got '${token.value}' at position ${token.position}`);
540
+ throw new DartQLParseError(`Expected comparison operator`, token.position, token.value);
541
+ }
542
+ this.consume();
543
+ return operator;
544
+ }
545
+ parseValue() {
546
+ const token = this.current();
547
+ if (token.type === TokenType.STRING) {
548
+ this.consume();
549
+ return token.value;
550
+ }
551
+ if (token.type === TokenType.NUMBER) {
552
+ this.consume();
553
+ return parseFloat(token.value);
554
+ }
555
+ if (token.type === TokenType.NULL) {
556
+ this.consume();
557
+ return null;
558
+ }
559
+ this.addError(`Expected value (string, number, or NULL), got '${token.value}' at position ${token.position}`);
560
+ throw new DartQLParseError('Expected value', token.position, token.value);
561
+ }
562
+ parseInArray() {
563
+ this.expect(TokenType.LPAREN, 'Expected opening parenthesis for IN clause');
564
+ const values = [];
565
+ if (this.match(TokenType.RPAREN)) {
566
+ this.consume();
567
+ return values;
568
+ }
569
+ values.push(this.parseValue());
570
+ while (this.match(TokenType.COMMA)) {
571
+ this.consume();
572
+ values.push(this.parseValue());
573
+ }
574
+ this.expect(TokenType.RPAREN, 'Expected closing parenthesis for IN clause');
575
+ return values;
576
+ }
577
+ }
578
+ export function parseDartQLToAST(input) {
579
+ try {
580
+ const tokenizer = new DartQLTokenizer(input);
581
+ const tokens = tokenizer.tokenize();
582
+ const lexer = new DartQLLexer(tokens);
583
+ const lexerResult = lexer.analyze();
584
+ if (lexerResult.errors.length > 0) {
585
+ return {
586
+ ast: { type: 'group', expressions: [] },
587
+ fields: lexerResult.fields,
588
+ errors: lexerResult.errors,
589
+ };
590
+ }
591
+ const parser = new DartQLParser(tokens);
592
+ const parseResult = parser.parse();
593
+ return parseResult;
594
+ }
595
+ catch (error) {
596
+ if (error instanceof DartQLParseError) {
597
+ return {
598
+ ast: { type: 'group', expressions: [] },
599
+ fields: [],
600
+ errors: [error.message],
601
+ };
602
+ }
603
+ throw error;
604
+ }
605
+ }
606
+ export function convertToFilters(ast) {
607
+ const result = {
608
+ apiFilters: {},
609
+ requiresClientSide: false,
610
+ warnings: [],
611
+ errors: [],
612
+ };
613
+ try {
614
+ const analysis = analyzeAST(ast);
615
+ if (analysis.canUseAPI) {
616
+ result.apiFilters = extractAPIFilters(ast);
617
+ }
618
+ else {
619
+ result.requiresClientSide = true;
620
+ result.clientFilter = buildClientSideFilter(ast);
621
+ result.warnings.push('Query requires client-side filtering which may impact performance. ' +
622
+ 'Consider using simpler queries with API-supported filters for better performance.');
623
+ if (analysis.reasons.length > 0) {
624
+ result.warnings.push(...analysis.reasons);
625
+ }
626
+ }
627
+ }
628
+ catch (error) {
629
+ result.errors.push(`Failed to convert AST to filters: ${error instanceof Error ? error.message : String(error)}`);
630
+ }
631
+ return result;
632
+ }
633
+ function analyzeAST(ast) {
634
+ const reasons = [];
635
+ const canUseAPI = isAPICompatible(ast, reasons);
636
+ return { canUseAPI, reasons };
637
+ }
638
+ function isAPICompatible(expr, reasons) {
639
+ if (expr.type === 'comparison') {
640
+ const field = expr.field?.toLowerCase();
641
+ const operator = expr.operator;
642
+ const apiSupportedFields = ['assignee', 'status', 'dartboard', 'priority', 'tags', 'due_at'];
643
+ if (field && !apiSupportedFields.includes(field)) {
644
+ reasons.push(`Field '${field}' not supported by API filters`);
645
+ return false;
646
+ }
647
+ if (operator === 'IN' || operator === 'NOT IN') {
648
+ reasons.push(`${operator} operator requires client-side filtering`);
649
+ return false;
650
+ }
651
+ if (operator === 'LIKE' || operator === 'CONTAINS') {
652
+ reasons.push(`${operator} operator requires client-side filtering`);
653
+ return false;
654
+ }
655
+ if (operator === 'IS NULL' || operator === 'IS NOT NULL') {
656
+ reasons.push(`${operator} operator requires client-side filtering`);
657
+ return false;
658
+ }
659
+ if (operator === 'BETWEEN') {
660
+ reasons.push(`BETWEEN operator requires client-side filtering`);
661
+ return false;
662
+ }
663
+ if (field === 'priority' && (operator === '>' || operator === '>=' || operator === '<' || operator === '<=')) {
664
+ reasons.push(`Range operators on 'priority' require client-side filtering (API only supports equality)`);
665
+ return false;
666
+ }
667
+ if (field === 'due_at') {
668
+ if (operator === '<' || operator === '<=') {
669
+ return true;
670
+ }
671
+ if (operator === '>' || operator === '>=') {
672
+ return true;
673
+ }
674
+ if (operator === '=' || operator === '!=') {
675
+ reasons.push(`Equality operators on 'due_at' require client-side filtering`);
676
+ return false;
677
+ }
678
+ }
679
+ if (operator !== '=' && operator !== '!=') {
680
+ reasons.push(`Operator '${operator}' not supported by API for field '${field}'`);
681
+ return false;
682
+ }
683
+ if (operator === '!=') {
684
+ reasons.push(`!= operator requires client-side filtering`);
685
+ return false;
686
+ }
687
+ return true;
688
+ }
689
+ if (expr.type === 'logical') {
690
+ if (expr.operator === 'OR') {
691
+ reasons.push('OR logic requires client-side filtering (API only supports AND)');
692
+ return false;
693
+ }
694
+ if (expr.operator === 'NOT') {
695
+ reasons.push('NOT logic requires client-side filtering');
696
+ return false;
697
+ }
698
+ if (expr.operator === 'AND') {
699
+ const leftOk = expr.left ? isAPICompatible(expr.left, reasons) : false;
700
+ const rightOk = expr.right ? isAPICompatible(expr.right, reasons) : false;
701
+ return leftOk && rightOk;
702
+ }
703
+ }
704
+ if (expr.type === 'group') {
705
+ if (expr.expressions && expr.expressions.length > 0) {
706
+ return expr.expressions.every(e => isAPICompatible(e, reasons));
707
+ }
708
+ }
709
+ return false;
710
+ }
711
+ function extractAPIFilters(expr) {
712
+ const filters = {};
713
+ if (expr.type === 'comparison') {
714
+ const field = expr.field?.toLowerCase();
715
+ const operator = expr.operator;
716
+ const value = expr.value;
717
+ if (field === 'assignee' && operator === '=') {
718
+ filters.assignee = value != null ? String(value) : '';
719
+ }
720
+ else if (field === 'status' && operator === '=') {
721
+ filters.status = value != null ? String(value) : '';
722
+ }
723
+ else if (field === 'dartboard' && operator === '=') {
724
+ filters.dartboard = value != null ? String(value) : '';
725
+ }
726
+ else if (field === 'priority' && operator === '=') {
727
+ const numValue = typeof value === 'number' ? value : parseFloat(String(value));
728
+ filters.priority = isNaN(numValue) ? 0 : numValue;
729
+ }
730
+ else if (field === 'tags' && operator === '=') {
731
+ filters.tags = value != null ? [String(value)] : [];
732
+ }
733
+ else if (field === 'due_at') {
734
+ if (operator === '<' || operator === '<=') {
735
+ filters.due_before = value != null ? String(value) : '';
736
+ }
737
+ else if (operator === '>' || operator === '>=') {
738
+ filters.due_after = value != null ? String(value) : '';
739
+ }
740
+ }
741
+ }
742
+ else if (expr.type === 'logical' && expr.operator === 'AND') {
743
+ const leftFilters = expr.left ? extractAPIFilters(expr.left) : {};
744
+ const rightFilters = expr.right ? extractAPIFilters(expr.right) : {};
745
+ Object.assign(filters, leftFilters, rightFilters);
746
+ }
747
+ else if (expr.type === 'group') {
748
+ if (expr.expressions && expr.expressions.length > 0) {
749
+ return extractAPIFilters(expr.expressions[0]);
750
+ }
751
+ }
752
+ return filters;
753
+ }
754
+ function buildClientSideFilter(expr) {
755
+ return (task) => {
756
+ return evaluateExpression(expr, task);
757
+ };
758
+ }
759
+ function evaluateExpression(expr, task) {
760
+ if (!task || typeof task !== 'object') {
761
+ return false;
762
+ }
763
+ const taskObj = task;
764
+ if (expr.type === 'comparison') {
765
+ const field = expr.field?.toLowerCase();
766
+ const operator = expr.operator;
767
+ const value = expr.value;
768
+ if (!field)
769
+ return false;
770
+ const taskValue = taskObj[field];
771
+ switch (operator) {
772
+ case '=':
773
+ return taskValue === value;
774
+ case '!=':
775
+ return taskValue !== value;
776
+ case '>':
777
+ return typeof taskValue === 'number' && typeof value === 'number' && taskValue > value;
778
+ case '>=':
779
+ return typeof taskValue === 'number' && typeof value === 'number' && taskValue >= value;
780
+ case '<':
781
+ if (typeof taskValue === 'number' && typeof value === 'number') {
782
+ return taskValue < value;
783
+ }
784
+ if (typeof taskValue === 'string' && typeof value === 'string') {
785
+ return taskValue < value;
786
+ }
787
+ return false;
788
+ case '<=':
789
+ if (typeof taskValue === 'number' && typeof value === 'number') {
790
+ return taskValue <= value;
791
+ }
792
+ if (typeof taskValue === 'string' && typeof value === 'string') {
793
+ return taskValue <= value;
794
+ }
795
+ return false;
796
+ case 'IN':
797
+ if (Array.isArray(value)) {
798
+ return value.includes(taskValue);
799
+ }
800
+ return false;
801
+ case 'NOT IN':
802
+ if (Array.isArray(value)) {
803
+ return !value.includes(taskValue);
804
+ }
805
+ return false;
806
+ case 'CONTAINS':
807
+ if (Array.isArray(taskValue) && typeof value === 'string') {
808
+ return taskValue.includes(value);
809
+ }
810
+ if (typeof taskValue === 'string' && typeof value === 'string') {
811
+ return taskValue.includes(value);
812
+ }
813
+ return false;
814
+ case 'LIKE':
815
+ if (typeof taskValue === 'string' && typeof value === 'string') {
816
+ const escaped = value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
817
+ const pattern = escaped.replace(/%/g, '.*').replace(/_/g, '.');
818
+ const regex = new RegExp(`^${pattern}$`, 'i');
819
+ return regex.test(taskValue);
820
+ }
821
+ return false;
822
+ case 'IS NULL':
823
+ if (Array.isArray(taskValue)) {
824
+ return taskValue.length === 0;
825
+ }
826
+ return taskValue === null || taskValue === undefined;
827
+ case 'IS NOT NULL':
828
+ if (Array.isArray(taskValue)) {
829
+ return taskValue.length > 0;
830
+ }
831
+ return taskValue !== null && taskValue !== undefined;
832
+ case 'BETWEEN':
833
+ if (Array.isArray(value) && value.length === 2) {
834
+ const [min, max] = value;
835
+ if (typeof taskValue === 'number' && typeof min === 'number' && typeof max === 'number') {
836
+ return taskValue >= min && taskValue <= max;
837
+ }
838
+ if (typeof taskValue === 'string' && typeof min === 'string' && typeof max === 'string') {
839
+ return taskValue >= min && taskValue <= max;
840
+ }
841
+ }
842
+ return false;
843
+ default:
844
+ return false;
845
+ }
846
+ }
847
+ else if (expr.type === 'logical') {
848
+ if (expr.operator === 'AND') {
849
+ const leftResult = expr.left ? evaluateExpression(expr.left, task) : false;
850
+ const rightResult = expr.right ? evaluateExpression(expr.right, task) : false;
851
+ return leftResult && rightResult;
852
+ }
853
+ else if (expr.operator === 'OR') {
854
+ const leftResult = expr.left ? evaluateExpression(expr.left, task) : false;
855
+ const rightResult = expr.right ? evaluateExpression(expr.right, task) : false;
856
+ return leftResult || rightResult;
857
+ }
858
+ else if (expr.operator === 'NOT') {
859
+ const result = expr.right ? evaluateExpression(expr.right, task) : false;
860
+ return !result;
861
+ }
862
+ }
863
+ else if (expr.type === 'group') {
864
+ if (expr.expressions && expr.expressions.length > 0) {
865
+ return evaluateExpression(expr.expressions[0], task);
866
+ }
867
+ }
868
+ return false;
869
+ }
870
+ export function parseDartQL(input) {
871
+ try {
872
+ const tokenizer = new DartQLTokenizer(input);
873
+ const tokens = tokenizer.tokenize();
874
+ const lexer = new DartQLLexer(tokens);
875
+ const result = lexer.analyze();
876
+ return result;
877
+ }
878
+ catch (error) {
879
+ if (error instanceof DartQLParseError) {
880
+ return {
881
+ tokens: [],
882
+ fields: [],
883
+ errors: [error.message],
884
+ };
885
+ }
886
+ throw error;
887
+ }
888
+ }
889
+ //# sourceMappingURL=dartql.js.map