Skip to content

Commit 31b6038

Browse files
Copilothotlong
andcommitted
Update CRM examples and Report schema to use modern MongoDB-style filters
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 29e2655 commit 31b6038

11 files changed

Lines changed: 554 additions & 1865 deletions

File tree

content/docs/references/data/FilterCondition.mdx

Lines changed: 1 addition & 424 deletions
Large diffs are not rendered by default.
Lines changed: 4 additions & 334 deletions
Original file line numberDiff line numberDiff line change
@@ -1,342 +1,12 @@
11
---
22
title: NormalizedFilter
3-
description: Internal AST representation of filter conditions after normalization
3+
description: NormalizedFilter Schema Reference
44
---
55

6-
# NormalizedFilter
7-
8-
NormalizedFilter is the internal Abstract Syntax Tree (AST) representation of filter conditions after converting all syntactic sugar to explicit operators. This simplified structure makes it easier for driver implementations to process filters consistently.
9-
10-
## Overview
11-
12-
During the normalization pass, implicit syntax is converted to explicit operator-based conditions:
13-
14-
**Input (User-friendly):**
15-
```typescript
16-
{ age: 18, role: "admin" }
17-
```
18-
19-
**Output (Normalized):**
20-
```typescript
21-
{
22-
$and: [
23-
{ age: { $eq: 18 } },
24-
{ role: { $eq: "admin" } }
25-
]
26-
}
27-
```
28-
29-
## Schema
30-
31-
```typescript
32-
{
33-
$and?: Array<FieldCondition | NormalizedFilter>,
34-
$or?: Array<FieldCondition | NormalizedFilter>,
35-
$not?: FieldCondition | NormalizedFilter
36-
}
37-
38-
type FieldCondition = Record<string, FieldOperators>
39-
```
40-
416
## Properties
427

438
| Property | Type | Required | Description |
449
| :--- | :--- | :--- | :--- |
45-
| **$and** | `Array<FieldCondition \| NormalizedFilter>` | optional | All conditions must be true (logical AND) |
46-
| **$or** | `Array<FieldCondition \| NormalizedFilter>` | optional | At least one condition must be true (logical OR) |
47-
| **$not** | `FieldCondition \| NormalizedFilter` | optional | Negates the condition (logical NOT) |
48-
49-
## Normalization Process
50-
51-
### Stage 1: Implicit Equality → Explicit $eq
52-
53-
**Before:**
54-
```typescript
55-
{ status: "active", verified: true }
56-
```
57-
58-
**After:**
59-
```typescript
60-
{
61-
$and: [
62-
{ status: { $eq: "active" } },
63-
{ verified: { $eq: true } }
64-
]
65-
}
66-
```
67-
68-
### Stage 2: Flatten Top-level AND
69-
70-
**Before:**
71-
```typescript
72-
{
73-
status: "active",
74-
age: { $gte: 18 },
75-
role: "admin"
76-
}
77-
```
78-
79-
**After:**
80-
```typescript
81-
{
82-
$and: [
83-
{ status: { $eq: "active" } },
84-
{ age: { $gte: 18 } },
85-
{ role: { $eq: "admin" } }
86-
]
87-
}
88-
```
89-
90-
### Stage 3: Preserve Logical Operators
91-
92-
**Before:**
93-
```typescript
94-
{
95-
status: "active",
96-
$or: [
97-
{ role: "admin" },
98-
{ permissions: { $contains: "write" } }
99-
]
100-
}
101-
```
102-
103-
**After:**
104-
```typescript
105-
{
106-
$and: [
107-
{ status: { $eq: "active" } },
108-
{
109-
$or: [
110-
{ role: { $eq: "admin" } },
111-
{ permissions: { $contains: "write" } }
112-
]
113-
}
114-
]
115-
}
116-
```
117-
118-
## Examples
119-
120-
### Simple AND Condition
121-
122-
**Input:**
123-
```typescript
124-
const filter: QueryFilter = {
125-
where: {
126-
status: "active",
127-
age: { $gte: 18 }
128-
}
129-
};
130-
```
131-
132-
**Normalized:**
133-
```typescript
134-
{
135-
$and: [
136-
{ status: { $eq: "active" } },
137-
{ age: { $gte: 18 } }
138-
]
139-
}
140-
```
141-
142-
### OR with Nested AND
143-
144-
**Input:**
145-
```typescript
146-
const filter: QueryFilter = {
147-
where: {
148-
$or: [
149-
{ role: "admin" },
150-
{
151-
$and: [
152-
{ verified: true },
153-
{ score: { $gt: 80 } }
154-
]
155-
}
156-
]
157-
}
158-
};
159-
```
160-
161-
**Normalized:**
162-
```typescript
163-
{
164-
$or: [
165-
{ role: { $eq: "admin" } },
166-
{
167-
$and: [
168-
{ verified: { $eq: true } },
169-
{ score: { $gt: 80 } }
170-
]
171-
}
172-
]
173-
}
174-
```
175-
176-
### NOT Condition
177-
178-
**Input:**
179-
```typescript
180-
const filter: QueryFilter = {
181-
where: {
182-
$not: {
183-
status: "deleted",
184-
archived: true
185-
}
186-
}
187-
};
188-
```
189-
190-
**Normalized:**
191-
```typescript
192-
{
193-
$not: {
194-
$and: [
195-
{ status: { $eq: "deleted" } },
196-
{ archived: { $eq: true } }
197-
]
198-
}
199-
}
200-
```
201-
202-
### Complex Nested Structure
203-
204-
**Input:**
205-
```typescript
206-
const filter: QueryFilter = {
207-
where: {
208-
status: "active",
209-
$and: [
210-
{ age: { $gte: 18 } },
211-
{
212-
$or: [
213-
{ role: "admin" },
214-
{ permissions: { $contains: "edit" } }
215-
]
216-
}
217-
],
218-
department: {
219-
name: "Engineering"
220-
}
221-
}
222-
};
223-
```
224-
225-
**Normalized:**
226-
```typescript
227-
{
228-
$and: [
229-
{ status: { $eq: "active" } },
230-
{ age: { $gte: 18 } },
231-
{
232-
$or: [
233-
{ role: { $eq: "admin" } },
234-
{ permissions: { $contains: "edit" } }
235-
]
236-
},
237-
{ "department.name": { $eq: "Engineering" } } // Flattened path
238-
]
239-
}
240-
```
241-
242-
## Benefits for Driver Implementation
243-
244-
### 1. Consistent Structure
245-
Every filter is guaranteed to have explicit operators, eliminating ambiguity.
246-
247-
### 2. Simplified Traversal
248-
Drivers can use a simple recursive pattern:
249-
```typescript
250-
function processFilter(filter: NormalizedFilter) {
251-
if (filter.$and) return processAnd(filter.$and);
252-
if (filter.$or) return processOr(filter.$or);
253-
if (filter.$not) return processNot(filter.$not);
254-
// Process field conditions
255-
}
256-
```
257-
258-
### 3. SQL Generation Example
259-
```typescript
260-
function toSQL(filter: NormalizedFilter): string {
261-
if (filter.$and) {
262-
return filter.$and.map(toSQL).join(' AND ');
263-
}
264-
if (filter.$or) {
265-
return filter.$or.map(toSQL).join(' OR ');
266-
}
267-
if (filter.$not) {
268-
return `NOT (${toSQL(filter.$not)})`;
269-
}
270-
// Handle field conditions
271-
}
272-
```
273-
274-
### 4. MongoDB Query Example
275-
```typescript
276-
function toMongo(filter: NormalizedFilter) {
277-
if (filter.$and) {
278-
return { $and: filter.$and.map(toMongo) };
279-
}
280-
if (filter.$or) {
281-
return { $or: filter.$or.map(toMongo) };
282-
}
283-
if (filter.$not) {
284-
return { $nor: [toMongo(filter.$not)] };
285-
}
286-
// Handle field conditions
287-
}
288-
```
289-
290-
## Field Operators in Normalized Filters
291-
292-
Each field condition uses explicit operators:
293-
294-
```typescript
295-
{
296-
$eq?: any, // Equal to
297-
$ne?: any, // Not equal to
298-
$gt?: number | Date, // Greater than
299-
$gte?: number | Date,// Greater than or equal
300-
$lt?: number | Date, // Less than
301-
$lte?: number | Date,// Less than or equal
302-
$in?: any[], // In array
303-
$nin?: any[], // Not in array
304-
$between?: [number | Date, number | Date], // Between range
305-
$contains?: string, // Contains substring
306-
$startsWith?: string,// Starts with prefix
307-
$endsWith?: string, // Ends with suffix
308-
$null?: boolean, // Is/isn't null
309-
$exist?: boolean // Field exists
310-
}
311-
```
312-
313-
## Usage in Driver Implementation
314-
315-
Drivers should normalize user input before processing:
316-
317-
```typescript
318-
import { normalizeFilter } from '@objectstack/spec';
319-
320-
class MyDriver implements Driver {
321-
async find(object: string, query: Query) {
322-
// Normalize filter
323-
const normalized = normalizeFilter(query.where);
324-
325-
// Convert to database-specific query
326-
const dbQuery = this.toNativeQuery(normalized);
327-
328-
// Execute
329-
return this.execute(dbQuery);
330-
}
331-
332-
private toNativeQuery(filter: NormalizedFilter) {
333-
// Driver-specific implementation
334-
}
335-
}
336-
```
337-
338-
## See Also
339-
340-
- [QueryFilter](./QueryFilter.mdx) - User-friendly filter syntax
341-
- [FilterCondition](./FilterCondition.mdx) - Recursive filter structure
342-
- [Driver Implementation Guide](/docs/guides/custom-driver.mdx) - Building custom drivers
10+
| **$and** | `Record<string, object> \| any[]` | optional | |
11+
| **$or** | `Record<string, object> \| any[]` | optional | |
12+
| **$not** | `Record<string, object> \| any` | optional | |

0 commit comments

Comments
 (0)