qast 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +260 -5
- package/README.zh-CN.md +520 -0
- package/dist/adapters/drizzle.d.ts +66 -0
- package/dist/adapters/drizzle.d.ts.map +1 -0
- package/dist/adapters/drizzle.js +222 -0
- package/dist/adapters/drizzle.js.map +1 -0
- package/dist/adapters/knex.d.ts +22 -0
- package/dist/adapters/knex.d.ts.map +1 -0
- package/dist/adapters/knex.js +158 -0
- package/dist/adapters/knex.js.map +1 -0
- package/dist/adapters/mongoose.d.ts +15 -0
- package/dist/adapters/mongoose.d.ts.map +1 -0
- package/dist/adapters/mongoose.js +152 -0
- package/dist/adapters/mongoose.js.map +1 -0
- package/dist/adapters/prisma.d.ts +5 -2
- package/dist/adapters/prisma.d.ts.map +1 -1
- package/dist/adapters/prisma.js +103 -4
- package/dist/adapters/prisma.js.map +1 -1
- package/dist/adapters/sequelize.d.ts +26 -8
- package/dist/adapters/sequelize.d.ts.map +1 -1
- package/dist/adapters/sequelize.js +168 -7
- package/dist/adapters/sequelize.js.map +1 -1
- package/dist/adapters/typeorm.d.ts +32 -2
- package/dist/adapters/typeorm.d.ts.map +1 -1
- package/dist/adapters/typeorm.js +169 -3
- package/dist/adapters/typeorm.js.map +1 -1
- package/dist/builder/query-builder.d.ts +139 -0
- package/dist/builder/query-builder.d.ts.map +1 -0
- package/dist/builder/query-builder.js +320 -0
- package/dist/builder/query-builder.js.map +1 -0
- package/dist/errors.d.ts +18 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +127 -9
- package/dist/errors.js.map +1 -1
- package/dist/index.d.ts +83 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +108 -8
- package/dist/index.js.map +1 -1
- package/dist/integrations/express.d.ts +14 -0
- package/dist/integrations/express.d.ts.map +1 -0
- package/dist/integrations/express.js +33 -0
- package/dist/integrations/express.js.map +1 -0
- package/dist/integrations/fastify.d.ts +17 -0
- package/dist/integrations/fastify.d.ts.map +1 -0
- package/dist/integrations/fastify.js +35 -0
- package/dist/integrations/fastify.js.map +1 -0
- package/dist/integrations/hono.d.ts +14 -0
- package/dist/integrations/hono.d.ts.map +1 -0
- package/dist/integrations/hono.js +30 -0
- package/dist/integrations/hono.js.map +1 -0
- package/dist/integrations/nestjs.d.ts +23 -0
- package/dist/integrations/nestjs.d.ts.map +1 -0
- package/dist/integrations/nestjs.js +137 -0
- package/dist/integrations/nestjs.js.map +1 -0
- package/dist/parser/parser.d.ts +15 -7
- package/dist/parser/parser.d.ts.map +1 -1
- package/dist/parser/parser.js +117 -15
- package/dist/parser/parser.js.map +1 -1
- package/dist/parser/tokenizer.d.ts +11 -1
- package/dist/parser/tokenizer.d.ts.map +1 -1
- package/dist/parser/tokenizer.js +67 -10
- package/dist/parser/tokenizer.js.map +1 -1
- package/dist/parser/validator.d.ts +39 -0
- package/dist/parser/validator.d.ts.map +1 -1
- package/dist/parser/validator.js +144 -3
- package/dist/parser/validator.js.map +1 -1
- package/dist/types/ast.d.ts +59 -3
- package/dist/types/ast.d.ts.map +1 -1
- package/dist/types/ast.js +21 -0
- package/dist/types/ast.js.map +1 -1
- package/dist/utils/ast-utils.d.ts +43 -0
- package/dist/utils/ast-utils.d.ts.map +1 -0
- package/dist/utils/ast-utils.js +205 -0
- package/dist/utils/ast-utils.js.map +1 -0
- package/dist/utils/cache.d.ts +47 -0
- package/dist/utils/cache.d.ts.map +1 -0
- package/dist/utils/cache.js +132 -0
- package/dist/utils/cache.js.map +1 -0
- package/dist/utils/serializer.d.ts +6 -0
- package/dist/utils/serializer.d.ts.map +1 -0
- package/dist/utils/serializer.js +140 -0
- package/dist/utils/serializer.js.map +1 -0
- package/package.json +29 -5
package/README.md
CHANGED
|
@@ -70,19 +70,35 @@ QAST supports the following comparison operators:
|
|
|
70
70
|
- `gte` - Greater than or equal
|
|
71
71
|
- `lte` - Less than or equal
|
|
72
72
|
- `in` - In array
|
|
73
|
+
- `notIn` - Not in array
|
|
73
74
|
- `contains` - Contains substring (string matching)
|
|
75
|
+
- `startsWith` - String starts with pattern
|
|
76
|
+
- `endsWith` - String ends with pattern
|
|
77
|
+
- `like` - Pattern matching with wildcards (`%` and `_`)
|
|
78
|
+
- `regex` / `matches` - Regex pattern matching
|
|
79
|
+
- `between` - Range check: `age between 18 and 65`
|
|
80
|
+
- `isNull` - Null check: `email isNull`
|
|
81
|
+
- `isNotNull` - Not null check: `email isNotNull`
|
|
74
82
|
|
|
75
83
|
### Logical Operators
|
|
76
84
|
|
|
77
85
|
- `and` - Logical AND
|
|
78
86
|
- `or` - Logical OR
|
|
87
|
+
- `not` - Logical NOT: `not (age gt 25)`
|
|
88
|
+
|
|
89
|
+
### Query Clauses
|
|
90
|
+
|
|
91
|
+
- **Sorting**: `orderBy age desc, name asc` - Sort by multiple fields
|
|
92
|
+
- **Pagination**: `limit 10 offset 20` - Limit and offset results
|
|
93
|
+
- **Nested fields**: `user.profile.name eq "John"` - Access nested fields with dot notation
|
|
79
94
|
|
|
80
95
|
### Values
|
|
81
96
|
|
|
82
97
|
- **Strings**: Use single or double quotes: `"John"` or `'John'`
|
|
83
98
|
- **Numbers**: Integers or floats: `25`, `25.99`, `-10`
|
|
84
99
|
- **Booleans**: `true` or `false`
|
|
85
|
-
- **
|
|
100
|
+
- **Null**: `null` - For null checks
|
|
101
|
+
- **Arrays**: For `in` and `notIn` operators: `[1,2,3]` or `["John","Jane"]`
|
|
86
102
|
|
|
87
103
|
### Examples
|
|
88
104
|
|
|
@@ -110,6 +126,19 @@ QAST supports the following comparison operators:
|
|
|
110
126
|
|
|
111
127
|
// Complex query
|
|
112
128
|
'age gt 25 and (name eq "John" or city eq "Paris") and active eq true'
|
|
129
|
+
|
|
130
|
+
// New operators
|
|
131
|
+
'age between 18 and 65'
|
|
132
|
+
'age notIn [1,2,3]'
|
|
133
|
+
'name like "John%"'
|
|
134
|
+
'email matches "^[a-z]+@example\.com$"'
|
|
135
|
+
'email isNull'
|
|
136
|
+
'email isNotNull'
|
|
137
|
+
'not (age gt 25)'
|
|
138
|
+
|
|
139
|
+
// Sorting and pagination
|
|
140
|
+
'age gt 25 orderBy age desc limit 10 offset 20'
|
|
141
|
+
'name eq "John" orderBy name asc, age desc limit 5'
|
|
113
142
|
```
|
|
114
143
|
|
|
115
144
|
## ORM Adapters
|
|
@@ -220,6 +249,192 @@ await User.findAll({ where: transformed });
|
|
|
220
249
|
|
|
221
250
|
**Note**: Sequelize uses the `Op` object from 'sequelize'. Since Sequelize is an optional peer dependency, the adapter returns a structure with metadata (`__qast_operator__` and `__qast_logical__`) that you need to transform to use `Op` operators. For simple equality (`eq`), the adapter returns plain values which Sequelize accepts directly.
|
|
222
251
|
|
|
252
|
+
### Mongoose
|
|
253
|
+
|
|
254
|
+
```typescript
|
|
255
|
+
import { parseQueryFull, toMongooseFilter } from 'qast';
|
|
256
|
+
|
|
257
|
+
const query = 'age gt 25 orderBy age desc limit 10';
|
|
258
|
+
const queryAST = parseQueryFull(query);
|
|
259
|
+
const filter = toMongooseFilter(queryAST);
|
|
260
|
+
|
|
261
|
+
// filter = {
|
|
262
|
+
// filter: { age: { $gt: 25 } },
|
|
263
|
+
// sort: { age: -1 },
|
|
264
|
+
// limit: 10
|
|
265
|
+
// }
|
|
266
|
+
|
|
267
|
+
await User.find(filter.filter)
|
|
268
|
+
.sort(filter.sort)
|
|
269
|
+
.limit(filter.limit);
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Knex.js
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { parseQueryFull, toKnexFilter } from 'qast';
|
|
276
|
+
|
|
277
|
+
const query = 'age gt 25 orderBy age desc limit 10';
|
|
278
|
+
const queryAST = parseQueryFull(query);
|
|
279
|
+
const filter = toKnexFilter(queryAST);
|
|
280
|
+
|
|
281
|
+
// Use with Knex query builder
|
|
282
|
+
knex('users')
|
|
283
|
+
.where(filter.whereCallback)
|
|
284
|
+
.orderBy(filter.orderBy!)
|
|
285
|
+
.limit(filter.limit!)
|
|
286
|
+
.offset(filter.offset);
|
|
287
|
+
```
|
|
288
|
+
|
|
289
|
+
### Drizzle ORM
|
|
290
|
+
|
|
291
|
+
```typescript
|
|
292
|
+
import { parseQueryFull, toDrizzleFilter, transformDrizzleConditions } from 'qast';
|
|
293
|
+
import { and, gt, eq, desc } from 'drizzle-orm';
|
|
294
|
+
|
|
295
|
+
const query = 'age gt 25 and name eq "John"';
|
|
296
|
+
const queryAST = parseQueryFull(query);
|
|
297
|
+
const filter = toDrizzleFilter(queryAST);
|
|
298
|
+
|
|
299
|
+
// Transform to Drizzle conditions
|
|
300
|
+
const columnMap = { age: users.age, name: users.name };
|
|
301
|
+
const where = transformDrizzleConditions(filter.where, { and, gt, eq }, columnMap);
|
|
302
|
+
|
|
303
|
+
await db.select().from(users).where(where);
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
## Query Builder API
|
|
307
|
+
|
|
308
|
+
Build queries programmatically using a fluent API:
|
|
309
|
+
|
|
310
|
+
```typescript
|
|
311
|
+
import { queryBuilder } from 'qast';
|
|
312
|
+
|
|
313
|
+
const query = queryBuilder()
|
|
314
|
+
.field('age').gt(25)
|
|
315
|
+
.and(queryBuilder().field('name').eq('John'))
|
|
316
|
+
.orderBy('age', 'desc')
|
|
317
|
+
.limit(10)
|
|
318
|
+
.offset(20)
|
|
319
|
+
.build();
|
|
320
|
+
|
|
321
|
+
// query.filter - the filter AST
|
|
322
|
+
// query.orderBy - sorting specifications
|
|
323
|
+
// query.limit - limit value
|
|
324
|
+
// query.offset - offset value
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
## AST Serialization
|
|
328
|
+
|
|
329
|
+
Convert AST back to query string:
|
|
330
|
+
|
|
331
|
+
```typescript
|
|
332
|
+
import { parseQuery, serializeQuery } from 'qast';
|
|
333
|
+
|
|
334
|
+
const query = 'age gt 25 and name eq "John"';
|
|
335
|
+
const ast = parseQuery(query);
|
|
336
|
+
const serialized = serializeQuery(ast);
|
|
337
|
+
// serialized = 'age gt 25 and name eq "John"'
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## AST Utilities
|
|
341
|
+
|
|
342
|
+
### Clone AST
|
|
343
|
+
|
|
344
|
+
```typescript
|
|
345
|
+
import { parseQuery, cloneAST } from 'qast';
|
|
346
|
+
|
|
347
|
+
const ast = parseQuery('age gt 25');
|
|
348
|
+
const cloned = cloneAST(ast);
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Merge Queries
|
|
352
|
+
|
|
353
|
+
```typescript
|
|
354
|
+
import { parseQuery, mergeQueries } from 'qast';
|
|
355
|
+
|
|
356
|
+
const query1 = parseQuery('age gt 25');
|
|
357
|
+
const query2 = parseQuery('name eq "John"');
|
|
358
|
+
const merged = mergeQueries(query1, query2, 'AND');
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
### Get Query Statistics
|
|
362
|
+
|
|
363
|
+
```typescript
|
|
364
|
+
import { parseQuery, getQueryStats } from 'qast';
|
|
365
|
+
|
|
366
|
+
const ast = parseQuery('age gt 25 and name eq "John"');
|
|
367
|
+
const stats = getQueryStats(ast);
|
|
368
|
+
// stats.depth, stats.nodeCount, stats.fields, etc.
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Optimize AST
|
|
372
|
+
|
|
373
|
+
```typescript
|
|
374
|
+
import { parseQuery, optimizeAST } from 'qast';
|
|
375
|
+
|
|
376
|
+
const ast = parseQuery('age gt 25 and age gt 25'); // Redundant
|
|
377
|
+
const optimized = optimizeAST(ast);
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### Compare Queries
|
|
381
|
+
|
|
382
|
+
```typescript
|
|
383
|
+
import { parseQuery, diffQueries } from 'qast';
|
|
384
|
+
|
|
385
|
+
const query1 = parseQuery('age gt 25');
|
|
386
|
+
const query2 = parseQuery('age gt 30');
|
|
387
|
+
const diff = diffQueries(query1, query2);
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
## Performance Features
|
|
391
|
+
|
|
392
|
+
### Query Caching
|
|
393
|
+
|
|
394
|
+
```typescript
|
|
395
|
+
import { QueryCache, createCachedParser, parseQuery } from 'qast';
|
|
396
|
+
|
|
397
|
+
// Create a cached parser
|
|
398
|
+
const cachedParse = createCachedParser(parseQuery, 100, 60000); // 100 items, 60s TTL
|
|
399
|
+
|
|
400
|
+
// Use cached parser
|
|
401
|
+
const ast = cachedParse('age gt 25'); // Cached on subsequent calls
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Security Features
|
|
405
|
+
|
|
406
|
+
### Field Sanitization
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
import { sanitizeFieldName } from 'qast';
|
|
410
|
+
|
|
411
|
+
const safeField = sanitizeFieldName('user.name'); // Removes dangerous characters
|
|
412
|
+
```
|
|
413
|
+
|
|
414
|
+
### Query Cost Estimation
|
|
415
|
+
|
|
416
|
+
```typescript
|
|
417
|
+
import { parseQuery, estimateQueryCost } from 'qast';
|
|
418
|
+
|
|
419
|
+
const ast = parseQuery('age gt 25 and name eq "John"');
|
|
420
|
+
const cost = estimateQueryCost(ast);
|
|
421
|
+
// cost.estimatedComplexity, cost.depth, cost.nodeCount, etc.
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Rate Limiting
|
|
425
|
+
|
|
426
|
+
```typescript
|
|
427
|
+
import { RateLimiter } from 'qast';
|
|
428
|
+
|
|
429
|
+
const limiter = new RateLimiter(100, 60000); // 100 requests per 60 seconds
|
|
430
|
+
|
|
431
|
+
if (limiter.isAllowed(userId)) {
|
|
432
|
+
// Process query
|
|
433
|
+
} else {
|
|
434
|
+
// Rate limit exceeded
|
|
435
|
+
}
|
|
436
|
+
```
|
|
437
|
+
|
|
223
438
|
## API Reference
|
|
224
439
|
|
|
225
440
|
### `parseQuery(query: string, options?: ParseOptions): QastNode`
|
|
@@ -268,13 +483,53 @@ Transform an AST to a TypeORM filter.
|
|
|
268
483
|
|
|
269
484
|
**Note:** TypeORM requires operator functions for non-equality comparisons. You may need to transform the result.
|
|
270
485
|
|
|
271
|
-
### `
|
|
486
|
+
### `parseQueryFull(query: string, options?: ParseOptions): QueryAST`
|
|
487
|
+
|
|
488
|
+
Parse a query string into a full QueryAST (includes filter, sorting, pagination).
|
|
489
|
+
|
|
490
|
+
**Returns:** QueryAST with filter, orderBy, limit, and offset
|
|
491
|
+
|
|
492
|
+
### `toPrismaFilter(ast: QastNode | QueryAST): PrismaFilter`
|
|
493
|
+
|
|
494
|
+
Transform an AST or QueryAST to a Prisma filter.
|
|
495
|
+
|
|
496
|
+
**Returns:** Prisma filter object with `where`, `orderBy`, `take`, and `skip` properties
|
|
497
|
+
|
|
498
|
+
### `toTypeORMFilter(ast: QastNode | QueryAST): TypeORMFilter`
|
|
499
|
+
|
|
500
|
+
Transform an AST or QueryAST to a TypeORM filter.
|
|
501
|
+
|
|
502
|
+
**Returns:** TypeORM filter object with `where`, `order`, `take`, and `skip` properties
|
|
503
|
+
|
|
504
|
+
**Note:** TypeORM requires operator functions for non-equality comparisons. Use `transformTypeORMOperators()` helper function.
|
|
505
|
+
|
|
506
|
+
### `toSequelizeFilter(ast: QastNode | QueryAST): SequelizeFilter`
|
|
507
|
+
|
|
508
|
+
Transform an AST or QueryAST to a Sequelize filter.
|
|
509
|
+
|
|
510
|
+
**Returns:** Sequelize filter object with `where`, `order`, `limit`, and `offset` properties
|
|
511
|
+
|
|
512
|
+
**Note:** Sequelize uses the `Op` object. Use `transformSequelizeOperators()` helper function.
|
|
513
|
+
|
|
514
|
+
### `toMongooseFilter(ast: QastNode | QueryAST): MongooseFilter`
|
|
515
|
+
|
|
516
|
+
Transform an AST or QueryAST to a Mongoose filter.
|
|
517
|
+
|
|
518
|
+
**Returns:** Mongoose filter object with `filter`, `sort`, `limit`, and `skip` properties
|
|
519
|
+
|
|
520
|
+
### `toKnexFilter(ast: QastNode | QueryAST): KnexFilter`
|
|
521
|
+
|
|
522
|
+
Transform an AST or QueryAST to a Knex filter.
|
|
523
|
+
|
|
524
|
+
**Returns:** Knex filter object with `whereCallback`, `orderBy`, `limit`, and `offset` properties
|
|
525
|
+
|
|
526
|
+
### `toDrizzleFilter(ast: QastNode | QueryAST): DrizzleFilter`
|
|
272
527
|
|
|
273
|
-
Transform an AST to a
|
|
528
|
+
Transform an AST or QueryAST to a Drizzle filter.
|
|
274
529
|
|
|
275
|
-
**Returns:**
|
|
530
|
+
**Returns:** Drizzle filter object with `where`, `orderBy`, `limit`, and `offset` properties
|
|
276
531
|
|
|
277
|
-
**Note:**
|
|
532
|
+
**Note:** Use `transformDrizzleConditions()` helper function to convert to actual Drizzle conditions.
|
|
278
533
|
|
|
279
534
|
### `validateQuery(ast: QastNode, whitelist: WhitelistOptions): void`
|
|
280
535
|
|