Add unified query DSL with MongoDB-style operators#52
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
|
This PR is very large. Consider breaking it into smaller PRs for easier review. |
There was a problem hiding this comment.
Pull request overview
This PR implements a JSON-based, MongoDB-style query filter DSL for database-agnostic data filtering. The design follows industry best practices from Prisma, Strapi, TypeORM, and LoopBack, providing both implicit equality syntax and explicit operator syntax with full TypeScript type safety.
Changes:
- New
filter.zod.tsschema with comprehensive operator support (comparison, set, range, string, logical, and special operators) - Type-safe generic
Filter<T>type with conditional type constraints for proper operator-field matching - Dual syntax support: implicit equality (
field: value) and explicit operators (field: { $op: value }) - Normalization layer (
NormalizedFilterSchema) to simplify adapter implementation - Comprehensive test suite with 622 lines covering all operators, edge cases, and real-world scenarios
- Auto-generated JSON schemas and MDX documentation
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 18 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/spec/src/index.ts | Exports the new filter module |
| packages/spec/src/data/filter.zod.ts | Core implementation with Zod schemas, TypeScript types, and operator definitions |
| packages/spec/src/data/filter.test.ts | Comprehensive test suite covering all operators and use cases |
| packages/spec/json-schema/*.json | Auto-generated JSON schemas for all filter-related types (9 files) |
| content/docs/references/data/*.mdx | Auto-generated MDX documentation files (9 files) |
|
|
||
| // Special | ||
| $null: z.boolean().optional(), | ||
| $exist: z.boolean().optional(), |
There was a problem hiding this comment.
The special operator is named $exist, but MongoDB's standard operator is $exists (with an 's'). For consistency with MongoDB conventions and industry standards, this should be $exists.
| $startsWith?: T[K] extends string ? string : never; | ||
| $endsWith?: T[K] extends string ? string : never; | ||
| $null?: boolean; | ||
| $exist?: boolean; |
There was a problem hiding this comment.
The property should be $exists (with an 's') to match MongoDB's standard operator name.
| $exist?: boolean; | |
| $exists?: boolean; |
| | Property | Type | Required | Description | | ||
| | :--- | :--- | :--- | :--- | | ||
| | **$null** | `boolean` | optional | | | ||
| | **$exist** | `boolean` | optional | | |
There was a problem hiding this comment.
The property should be $exists (with an 's') to match MongoDB's standard operator name.
| | **$exist** | `boolean` | optional | | | |
| | **$exists** | `boolean` | optional | | |
| | **$startsWith** | `string` | optional | | | ||
| | **$endsWith** | `string` | optional | | | ||
| | **$null** | `boolean` | optional | | | ||
| | **$exist** | `boolean` | optional | | |
There was a problem hiding this comment.
The property should be $exists (with an 's') to match MongoDB's standard operator name.
| | **$exist** | `boolean` | optional | | | |
| | **$exists** | `boolean` | optional | | |
| * ``` | ||
| */ | ||
| export const QueryFilterSchema = z.object({ | ||
| where: FilterConditionSchema.optional(), |
There was a problem hiding this comment.
Add .describe() call to the where property to populate documentation. For example: where: FilterConditionSchema.optional().describe('Filter conditions for the query').
| where: FilterConditionSchema.optional(), | |
| where: FilterConditionSchema.optional().describe('Filter conditions for the query'), |
| export const SpecialOperatorSchema = z.object({ | ||
| /** Is null check - SQL: IS NULL (true) / IS NOT NULL (false) | MongoDB: field: null */ | ||
| $null: z.boolean().optional(), | ||
|
|
||
| /** Field exists check (primarily for NoSQL) - MongoDB: $exists */ | ||
| $exist: z.boolean().optional(), | ||
| }); |
There was a problem hiding this comment.
Add .describe() calls to each property to populate the documentation descriptions. For example: $null: z.boolean().optional().describe('Is null check').
| $between: z.tuple([ | ||
| z.union([z.number(), z.date()]), | ||
| z.union([z.number(), z.date()]) | ||
| ]).optional(), |
There was a problem hiding this comment.
Add .describe() call to the property to populate the documentation description. For example: $between: z.tuple([...]).optional().describe('Between (inclusive)').
| ]).optional(), | |
| ]).optional().describe('Between (inclusive)'), |
| export const StringOperatorSchema = z.object({ | ||
| /** Contains substring - SQL: LIKE %?% | MongoDB: $regex */ | ||
| $contains: z.string().optional(), | ||
|
|
||
| /** Starts with prefix - SQL: LIKE ?% | MongoDB: $regex */ | ||
| $startsWith: z.string().optional(), | ||
|
|
||
| /** Ends with suffix - SQL: LIKE %? | MongoDB: $regex */ | ||
| $endsWith: z.string().optional(), | ||
| }); |
There was a problem hiding this comment.
Add .describe() calls to each property to populate the documentation descriptions. For example: $contains: z.string().optional().describe('Contains substring').
| $and: z.array(FilterConditionSchema).optional(), | ||
| $or: z.array(FilterConditionSchema).optional(), | ||
| $not: FilterConditionSchema.optional(), |
There was a problem hiding this comment.
Add .describe() calls to the logical operator properties to populate documentation. For example: $and: z.array(FilterConditionSchema).optional().describe('Logical AND - combines all conditions that must be true').
| $and: z.array(FilterConditionSchema).optional(), | |
| $or: z.array(FilterConditionSchema).optional(), | |
| $not: FilterConditionSchema.optional(), | |
| $and: z | |
| .array(FilterConditionSchema) | |
| .optional() | |
| .describe('Logical AND - combines all conditions that must be true'), | |
| $or: z | |
| .array(FilterConditionSchema) | |
| .optional() | |
| .describe('Logical OR - at least one condition must be true'), | |
| $not: FilterConditionSchema | |
| .optional() | |
| .describe('Logical NOT - negates the condition'), |
| $null: z.boolean().optional(), | ||
|
|
||
| /** Field exists check (primarily for NoSQL) - MongoDB: $exists */ | ||
| $exist: z.boolean().optional(), |
There was a problem hiding this comment.
The special operator is named $exist, but MongoDB's standard operator is $exists (with an 's'). For consistency with MongoDB conventions mentioned in the file header and to align with industry standards, this should be $exists.
| $exist: z.boolean().optional(), | |
| $exists: z.boolean().optional(), |
Implements a JSON-based, database-agnostic query filter syntax following Prisma/Strapi/TypeORM conventions for frontend-backend data filtering.
Changes
New schema:
filter.zod.tswith recursive filter structure$eq,$ne,$gt,$gte,$lt,$lte$in,$nin,$between$contains,$startsWith,$endsWith$and,$or,$not$null,$existType-safe generics:
Filter<T>with conditional type constraintsDual syntax: Implicit equality (
field: value) and explicit operators (field: { $op: value })Nested relations: JSON structure maps to JOIN operations
Normalization layer:
NormalizedFilterSchemafor adapter implementationExample
The schema generates JSON schemas and MDX documentation automatically via existing build pipeline.
Original prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.