npm package: @jsonql-standard/jsonql-parser
Description: A library to parse and validate JSONQL queries against the schema, useful for implementations
Latest version: 1.0.0

# jsonql-parser

[![npm version](https://img.shields.io/npm/v/@jsonql-standard/jsonql-parser.svg)](https://www.npmjs.com/package/@jsonql-standard/jsonql-parser)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A TypeScript/JavaScript library to parse and validate **JSONQL v1.0** queries against schemas. JSONQL is a secure, lightweight, and polyglot JSON-based query language for filtering, sorting, pagination, and field selection in RESTful APIs.

**Website**: [https://jsonql.org](https://jsonql.org)  
**Specification**: [github.com/JSONQL-Standard/jsonql-spec](https://github.com/JSONQL-Standard/jsonql-spec)

## Features

- ✅ Full JSONQL v1.0 specification support
- 🔒 Secure by design (parameterized, no regex injection, no code execution)
- 📝 TypeScript support with full type definitions
- ✔️ Schema validation for fields and relations
- 🏗️ Fluent query builder API
- 🧪 Comprehensive test coverage (76+ tests)
- 🔧 Configurable parsing options (max depth, max limit, field allowlists)
- 📦 Zero dependencies (dev dependencies only)
- 🌐 Framework and database agnostic

## Installation

```bash
npm install @jsonql-standard/jsonql-parser
```

Or with yarn:

```bash
yarn add @jsonql-standard/jsonql-parser
```

## Quick Start

### Basic Parsing

```typescript
import { JSONQLParser } from '@jsonql-standard/jsonql-parser';

const parser = new JSONQLParser();

// Parse a JSONQL query
const query = parser.parse({
  version: '1.0',
  where: {
    age: { gte: 18 },
    status: { eq: 'active' }
  },
  sort: ['name', '-created_at'],
  limit: 10
});

console.log(query);
```

### With Validation

```typescript
import { JSONQL } from 'jsonql-parser';

// Define your schema
const schema = {
  users: {
    fields: {
      id: { type: 'number', required: true },
      name: { type: 'string', required: true },
      email: { type: 'string', required: true },
      age: { type: 'number' },
      status: { type: 'string' }
    },
    relations: {
      posts: { type: 'hasMany', target: 'posts' }
    }
  }
};

// Create JSONQL instance with schema
const jsonql = new JSONQL(schema, 'users');

// Parse and validate
const result = jsonql.parseAndValidate({
  version: '1.0',
  fields: ['id', 'name', 'email'],
  where: {
    age: { gte: 18 }
  },
  limit: 10
});

if (result.validation.valid) {
  console.log('Query is valid!', result.query);
} else {
  console.error('Validation errors:', result.validation.errors);
}
```

### Query Builder

```typescript
import { JSONQLQueryBuilder, field, gte, eq, and } from 'jsonql-parser';

const builder = new JSONQLQueryBuilder();

const query = builder
  .fields('id', 'name', 'email')
  .where(
    and(
      field('age', gte(18)),
      field('status', eq('active'))
    )
  )
  .sort('name', '-created_at')
  .limit(10)
  .skip(0)
  .include('posts')
  .build();

console.log(JSON.stringify(query, null, 2));
```

## JSONQL v1.0 Query Structure

```json
{
  "version": "1.0",
  "where": { /* conditions */ },
  "sort": "field" | ["field", "-field"],
  "limit": 100,
  "skip": 0,
  "fields": ["id", "name"],
  "include": ["author", "tags"]
}
```

### Supported Operators

| Operator | Description | Example |
|----------|-------------|---------|
| `eq` | Equal to | `{ "status": { "eq": "active" } }` |
| `ne` | Not equal to | `{ "status": { "ne": "deleted" } }` |
| `gt` | Greater than | `{ "age": { "gt": 18 } }` |
| `gte` | Greater than or equal | `{ "age": { "gte": 18 } }` |
| `lt` | Less than | `{ "price": { "lt": 100 } }` |
| `lte` | Less than or equal | `{ "price": { "lte": 100 } }` |
| `in` | In array | `{ "id": { "in": [1, 2, 3] } }` |
| `nin` | Not in array | `{ "status": { "nin": ["spam", "deleted"] } }` |
| `contains` | Contains substring | `{ "name": { "contains": "john" } }` |
| `starts` | Starts with prefix | `{ "email": { "starts": "admin" } }` |
| `ends` | Ends with suffix | `{ "email": { "ends": "@company.com" } }` |

### Logical Operators

```typescript
// AND
{
  "and": [
    { "age": { "gte": 18 } },
    { "status": { "eq": "active" } }
  ]
}

// OR
{
  "or": [
    { "role": { "eq": "admin" } },
    { "role": { "eq": "moderator" } }
  ]
}

// NOT
{
  "not": {
    "status": { "eq": "deleted" }
  }
}
```

### Field-to-Field Comparisons

```typescript
// Compare two fields
{
  "where": {
    "price": { "gt": { "field": "cost" } }
  }
}

// With nested fields (requires include)
{
  "where": {
    "startDate": { "lt": { "field": "author.createDate" } }
  },
  "include": ["author"]
}
```

## API Reference

### JSONQLParser

```typescript
const parser = new JSONQLParser(options?);

// Options
interface JSONQLParserOptions {
  maxNestingDepth?: number;  // Default: 5
  maxLimit?: number;          // Default: 1000
  allowedFields?: string[];   // Field allowlist
  allowedIncludes?: string[]; // Relation allowlist
}

// Methods
parser.parse(input: string | object): JSONQLQuery
parser.stringify(query: JSONQLQuery): string
```

### JSONQLValidator

```typescript
const validator = new JSONQLValidator(schema, tableName);

validator.validate(query: JSONQLQuery): ValidationResult
validator.setSchema(schema: JSONQLSchema, tableName: string): void
validator.getSchema(): JSONQLSchema
validator.getTableName(): string
```

### JSONQLQueryBuilder

```typescript
const builder = new JSONQLQueryBuilder();

// Fluent API
builder
  .where(condition: JSONQLWhere)
  .andWhere(condition: JSONQLWhere)
  .orWhere(condition: JSONQLWhere)
  .sort(...fields: string[])
  .limit(n: number)
  .skip(n: number)
  .fields(...fields: string[])
  .include(...relations: string[])
  .build(): JSONQLQuery
  .reset(): this
```

### Helper Functions

```typescript
// Condition helpers
import { 
  field, eq, ne, gt, gte, lt, lte, 
  inArray, nin, contains, starts, ends,
  fieldRef, and, or, not
} from 'jsonql-parser';

// Build conditions
field('age', gte(18))
// => { "age": { "gte": 18 } }

and(
  field('age', gte(18)),
  field('status', eq('active'))
)
// => { "and": [...] }

// Field reference
field('price', gt(fieldRef('cost')))
// => { "price": { "gt": { "field": "cost" } } }
```

## Examples

### Complex Query

```typescript
const query = {
  version: '1.0',
  fields: ['id', 'name', 'email'],
  where: {
    and: [
      { age: { gte: 18 } },
      {
        or: [
          { role: { eq: 'admin' } },
          { email: { ends: '@company.com' } }
        ]
      },
      { status: { ne: 'deleted' } }
    ]
  },
  sort: ['name', '-created_at'],
  limit: 10,
  skip: 0,
  include: ['posts', 'profile']
};

const result = jsonql.parseAndValidate(query);
```

### Using Query Builder

```typescript
import { JSONQLQueryBuilder, field, gte, eq, ends, or, and } from 'jsonql-parser';

const query = new JSONQLQueryBuilder()
  .fields('id', 'name', 'email')
  .where(
    and(
      field('age', gte(18)),
      or(
        field('role', eq('admin')),
        field('email', ends('@company.com'))
      )
    )
  )
  .sort('name', '-created_at')
  .limit(10)
  .include('posts', 'profile')
  .build();
```

## Security Best Practices

1. **Validate all queries** against a schema
2. **Use field allowlists** to restrict accessible fields
3. **Limit nesting depth** (default: 5) to prevent DoS
4. **Cap limit** (default: 1000) to prevent resource exhaustion
5. **Rate-limit** complex queries
6. **Use parameterized queries** when converting to SQL/NoSQL

## Development

```bash
# Install dependencies
npm install

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Build
npm run build

# Lint
npm run lint

# Format
npm run format
```

## Contributing

Contributions are welcome! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details.

## License

MIT © JSONQL Standard

## Related Projects

- [jsonql-spec](https://github.com/JSONQL-Standard/jsonql-spec) - JSONQL specification
- [jsonql.org](https://jsonql.org) - Official website

## Support

- 📖 [Documentation](https://jsonql.org/docs)
- 💬 [Discord Community](https://discord.gg/jsonql)
- 🐛 [Issue Tracker](https://github.com/JSONQL-Standard/jsonql-parser/issues)