2.0.2 • Published 5 months ago
@origins-digital/query-filter-core v2.0.2
@origins-digital/query-filter-core
A core package for building type-safe query filters in TypeScript.
Installation
npm install @origins-digital/query-filter-coreFeatures
- Type-safe query filtering
- Support for complex filter conditions
- Built-in comparison operators
- OpenAPI/Swagger integration
- Flexible filter grouping (AND/OR)
- Support for nested object filtering
- Zod schema validation
Usage
Basic Filtering with Zod
import { QueryFilter } from '@origins-digital/query-filter-core';
import { z } from 'zod';
// Define your schema with Zod
const UserSchema = z.object({
id: z.string(),
name: z.string(),
age: z.number(),
isActive: z.boolean(),
});
type User = z.infer<typeof UserSchema>;
// Simple equality filter
const filter: QueryFilter<User> = {
name: { eq: 'John' },
age: { gt: 18 },
isActive: { is: true },
};
// Validate filter with Zod
const validateFilter = (filter: unknown) => {
return UserSchema.parse(filter);
};Complex Filtering with Zod
import { z } from 'zod';
import { QueryFilter } from '@origins-digital/query-filter-core';
// Define nested schemas
const ProfileSchema = z.object({
bio: z.string(),
location: z.string(),
});
const UserSchema = z.object({
id: z.string(),
name: z.string(),
age: z.number(),
isActive: z.boolean(),
profile: ProfileSchema,
});
type User = z.infer<typeof UserSchema>;
// Complex filter with nested objects
const filter: QueryFilter<User> = {
or: [
{ name: { contains: 'John' } },
{
and: [
{ age: { gt: 18 } },
{ isActive: { is: true } },
{ profile: { bio: { contains: 'developer' } } },
],
},
],
};Using ApiQuerifyZodSchema and ApiQueryZodSchema
import { z } from 'zod';
import {
ApiQuerifyZodSchema,
ApiQueryZodSchema,
} from '@origins-digital/query-filter-core';
// Define your entity schema with Zod
const UserSchema = z.object({
name: z.string().describe('User full name'),
age: z.number().describe('User age in years'),
isActive: z.boolean().describe('Whether the user is active'),
createdAt: z.date().describe('User creation date'),
profile: z.object({
bio: z.string().max(500).describe('User biography'),
location: z.string().describe('User location'),
}),
});
// Define query parameters schema
const UserQueryParamsSchema = z.object({
page: z.number().min(1).default(1).describe('Page number'),
limit: z.number().min(1).max(100).default(10).describe('Items per page'),
sortBy: z
.enum(['name', 'age', 'createdAt'])
.default('createdAt')
.describe('Field to sort by'),
sortOrder: z.enum(['asc', 'desc']).default('desc').describe('Sort order'),
});
// Controller usage
import { Controller, Get, Query } from '@nestjs/common';
import { ApiQuery } from '@nestjs/swagger';
import { QueryFilter } from '@origins-digital/query-filter-core';
@Controller('users')
export class UserController {
@Get()
@ApiQuerifyZodSchema(UserSchema, {
description: 'Filter users by various criteria',
example: {
name: { contains: 'John' },
age: { gt: 18 },
isActive: { is: true },
},
})
async getUsers(
@Query('filter') filter?: QueryFilter<z.infer<typeof UserSchema>>,
) {
if (filter) {
// Your implementation here
}
}
@Get('search')
@ApiQueryZodSchema(UserQueryParamsSchema)
async searchUsers(@Query() query: z.infer<typeof UserQueryParamsSchema>) {
// Your implementation here
// query.page, query.limit, query.sortBy, query.sortOrder are typed
}
}Custom Zod Validations
import { z } from 'zod';
import { QueryFilter } from '@origins-digital/query-filter-core';
// Define custom validations
const UserSchema = z.object({
name: z.string().min(2).max(50),
age: z.number().min(0).max(120),
email: z.string().email(),
isActive: z.boolean(),
createdAt: z.date(),
profile: z.object({
bio: z.string().max(500),
location: z.string(),
}),
});
// Create filter validation schema
const FilterSchema = z.object({
name: z
.object({
eq: z.string().optional(),
contains: z.string().optional(),
startsWith: z.string().optional(),
endsWith: z.string().optional(),
})
.optional(),
age: z
.object({
gt: z.number().optional(),
lt: z.number().optional(),
gte: z.number().optional(),
lte: z.number().optional(),
in: z.array(z.number()).optional(),
})
.optional(),
isActive: z
.object({
is: z.boolean().optional(),
isNot: z.boolean().optional(),
})
.optional(),
profile: z
.object({
bio: z
.object({
contains: z.string().optional(),
})
.optional(),
})
.optional(),
});
// Validate filter
const validateFilter = (filter: unknown) => {
return FilterSchema.parse(filter);
};API Reference
QueryFilter
type QueryFilter<T> = {
and?: QueryFilter<T>[];
or?: QueryFilter<T>[];
[K in keyof T]?: FilterFieldComparison<T[K]>;
};FilterFieldComparison
type FilterFieldComparison<FieldType> = {
// Common operators
eq?: FieldType;
neq?: FieldType;
gt?: FieldType;
gte?: FieldType;
lt?: FieldType;
lte?: FieldType;
in?: FieldType[];
notIn?: FieldType[];
is?: FieldType | null;
isNot?: FieldType | null;
// String specific operators
contains?: string;
startsWith?: string;
endsWith?: string;
mode?: 'default' | 'insensitive';
};Filter Operators
Common Operators
eq: Equal toneq: Not equal togt: Greater thangte: Greater than or equal tolt: Less thanlte: Less than or equal toin: In arraynotIn: Not in arrayis: Is (for boolean and null)isNot: Is not (for boolean and null)
String Operators
contains: Contains substringstartsWith: Starts withendsWith: Ends withmode: Case sensitivity ('default' or 'insensitive')
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.