bruno-query
Version:
Create/Parse query parameter for optimus/bruno php
228 lines (175 loc) • 7.81 kB
Markdown
# Bruno Query
[](https://www.npmjs.com/package/bruno-query)
[](https://www.npmjs.com/package/bruno-query)
[](LICENSE)
A TypeScript library for building and parsing query parameters, designed for Bruno PHP library.
## Features
- ✅ **Dynamic Optional Parameters** - Support single objects and arrays with custom keys
- ✅ **Multiple Build Formats** - TypeScript, ES Modules, and CommonJS
- ✅ **Type Safety** - Full TypeScript support with interfaces and enums
- ✅ **Flexible Query Building** - Includes, sorting, filtering, pagination
- ✅ **URL Generation** - Convert queries to URL strings
- ✅ **Query Parsing** - Parse existing query strings back to objects
## Installation
```bash
npm install bruno-query
```
## Quick Start
```typescript
import { BrunoQuery, SortDirection, QueryOperator } from 'bruno-query';
// Create a query
const query = new BrunoQuery()
.addIncludes('author', 'publisher')
.addSort({ key: 'name', direction: SortDirection.ASC })
.setLimit(20)
.setPage(1)
.setOptional({
category: 'books',
status: 'active'
});
// Get query string
const queryString = query.toQueryString();
// Result: "includes[]=author&includes[]=publisher&sort[0][key]=name&sort[0][direction]=ASC&limit=20&page=1&category=books&status=active"
// Get URL
const url = query.toURL('https://api.example.com/books');
// Result: "https://api.example.com/books?includes[]=author&includes[]=publisher&sort[0][key]=name&sort[0][direction]=ASC&limit=20&page=1&category=books&status=active"
```
## Usage Examples
### Basic Query
```typescript
import { BrunoQuery } from 'bruno-query';
const query = new BrunoQuery()
.addIncludes('author')
.setLimit(10)
.setPage(1);
console.log(query.toQueryString());
// "includes[]=author&limit=10&page=1"
```
### Advanced Query with Filters
```typescript
import { BrunoQuery, QueryOperator } from 'bruno-query';
const query = new BrunoQuery()
.addFilterGroup({
or: false,
filters: [
{ key: 'category', operator: QueryOperator.equals, value: 'books' },
{ key: 'status', operator: QueryOperator.equals, value: 'active' }
]
})
.addSort({ key: 'created_at', direction: 'DESC' })
.setOptional({
filters: {
category: 'books',
status: 'active'
},
options: {
includeDeleted: false
}
});
console.log(query.toQueryString());
```
### Parse Existing Query
```typescript
import { BrunoQuery } from 'bruno-query';
const queryString = "includes[]=author&sort[0][key]=name&limit=20&category=books";
const query = BrunoQuery.fromQueryString(queryString);
console.log(query.toObject());
// {
// includes: ['author'],
// sort: [{ key: 'name', direction: 'ASC' }],
// limit: 20,
// page: 1,
// filter_groups: [],
// optional: { category: 'books' }
// }
```
### JSON Serialization
```typescript
import { BrunoQuery } from 'bruno-query';
const query = new BrunoQuery()
.addIncludes('user', 'posts')
.addSort({ key: 'name', direction: 'ASC' })
.setLimit(10)
.setOptional({ custom_param: 'value', another_param: 123 });
// Convert to JSON string (optional parameters are flattened)
const jsonString = query.toJSON();
console.log(jsonString);
// {"includes":["user","posts"],"sort":[{"key":"name","direction":"ASC"}],"limit":10,"custom_param":"value","another_param":123}
// Parse JSON back to BrunoQuery (non-core parameters moved to optional)
const parsedQuery = BrunoQuery.fromJSON(jsonString);
console.log(parsedQuery.toObject());
// {
// includes: ['user', 'posts'],
// sort: [{ key: 'name', direction: 'ASC' }],
// limit: 10,
// optional: { custom_param: 'value', another_param: 123 }
// }
console.log(parsedQuery.toQueryString());
// includes[]=user&includes[]=posts&sort[0][key]=name&sort[0][direction]=ASC&limit=10&custom_param=value&another_param=123
```
**Note**: JSON serialization flattens optional parameters to the root level, ensuring consistency between JSON and query string representations.
### Filter Deduplication
BrunoQuery automatically removes duplicate filters within the same filter group based on the key-value-operator-not combination. This ensures clean and efficient query strings without redundant filters.
```typescript
import { BrunoQuery, QueryOperator } from 'bruno-query';
const query = new BrunoQuery()
.addFilterGroup({
or: false,
filters: [
{ key: 'name', value: 'John', operator: QueryOperator.contains },
{ key: 'name', value: 'John', operator: QueryOperator.contains }, // Duplicate - will be removed
{ key: 'age', value: 25, operator: QueryOperator.equals },
{ key: 'name', value: 'John', operator: QueryOperator.contains }, // Another duplicate - will be removed
{ key: 'status', value: 'active', operator: QueryOperator.equals }
]
});
console.log(query.toObject().filter_groups[0].filters.length);
// 3 (duplicates removed)
console.log(query.toQueryString());
// filter_groups[0][filters][0][key]=name&filter_groups[0][filters][0][value]=John&filter_groups[0][filters][0][operator]=ct&filter_groups[0][filters][1][key]=age&filter_groups[0][filters][1][value]=25&filter_groups[0][filters][1][operator]=eq&filter_groups[0][filters][2][key]=status&filter_groups[0][filters][2][value]=active&filter_groups[0][filters][2][operator]=eq
```
**Note**: Deduplication only applies within the same filter group. Different filter groups can have the same filters, and filters with different values, operators, or `not` values are not considered duplicates.
## API Reference
### Core Methods
- `addIncludes(...includes: string[])` - Add related resources to load
- `addArrayIncludes(includes: string[])` - Add includes from array
- `setIncludes(...includes: string[])` - Replace includes
- `setArrayIncludes(includes: string[])` - Replace includes from array
- `addSort(...sort: SortRule[])` - Add sorting rules
- `addArraySort(sort: SortRule[])` - Add sorting rules from array
- `setSort(...sort: SortRule[])` - Replace sorting rules
- `setArraySort(sort: SortRule[])` - Replace sorting rules from array
- `addFilterGroup(...groups: FilterGroup[])` - Add filter groups (with automatic deduplication)
- `addArrayFilterGroup(groups: FilterGroup[])` - Add filter groups from array (with automatic deduplication)
- `setFilterGroup(...groups: FilterGroup[])` - Replace filter groups (with automatic deduplication)
- `setArrayFilterGroup(groups: FilterGroup[])` - Replace filter groups from array (with automatic deduplication)
- `setLimit(limit: number)` - Set result limit
- `setPage(page: number)` - Set page number
- `setOptional(optional: Record<string, any> | Record<string, any>[])` - Set optional parameters
- `addOptional(optional: Record<string, any> | Record<string, any>[])` - Add/merge optional parameters
- `reset()` - Reset all parameters to defaults
### Output Methods
- `toQueryString()` - Get query string
- `get()` - Alias of `toQueryString()`
- `toURL(baseUrl?: string)` - Get full URL
- `toObject()` - Get object representation
- `toJSON()` - Get JSON string representation
- `clone()` - Create a copy of the query
### Static Methods
- `BrunoQuery.fromQueryString(queryString: string)` - Parse query string
- `BrunoQuery.fromJSON(jsonString: string)` - Parse JSON string
- `BrunoQuery.parse(queryString: string)` - Alias of `fromQueryString`
- `BrunoQuery.build(...)` - Build query with parameters
## Build Configuration
The library is built into 3 formats: cjs, mjs, d.ts
### Build Commands
```bash
# Build all formats
npm run build
# Development mode
npm run dev
# Test build structure
npm run test
```
## License
[MIT](LICENSE)