@@ -18,6 +18,66 @@ import {
1818 createErrorFromResponse ,
1919} from './errors' ;
2020
21+ /**
22+ * Map human-readable filter operator names produced by SDUI view configs
23+ * (e.g. `lead.view.ts`) to the canonical operator symbols expected by the
24+ * ObjectStack server's filter AST. Unknown operators fall through unchanged
25+ * so existing AST-style entries keep working.
26+ */
27+ const FILTER_OPERATOR_ALIASES : Record < string , string > = {
28+ equals : '=' ,
29+ eq : '=' ,
30+ '==' : '=' ,
31+ not_equals : '!=' ,
32+ notequals : '!=' ,
33+ ne : '!=' ,
34+ greater_than : '>' ,
35+ greaterthan : '>' ,
36+ gt : '>' ,
37+ greater_than_or_equal : '>=' ,
38+ greater_than_or_equals : '>=' ,
39+ greaterthanorequal : '>=' ,
40+ gte : '>=' ,
41+ less_than : '<' ,
42+ lessthan : '<' ,
43+ lt : '<' ,
44+ less_than_or_equal : '<=' ,
45+ less_than_or_equals : '<=' ,
46+ lessthanorequal : '<=' ,
47+ lte : '<=' ,
48+ in : 'in' ,
49+ not_in : 'nin' ,
50+ notin : 'nin' ,
51+ nin : 'nin' ,
52+ contains : 'contains' ,
53+ not_contains : 'notcontains' ,
54+ notcontains : 'notcontains' ,
55+ starts_with : 'startswith' ,
56+ startswith : 'startswith' ,
57+ ends_with : 'endswith' ,
58+ endswith : 'endswith' ,
59+ between : 'between' ,
60+ is_null : 'isnull' ,
61+ isnull : 'isnull' ,
62+ is_not_null : 'isnotnull' ,
63+ isnotnull : 'isnotnull' ,
64+ } ;
65+
66+ function normalizeFilterOperator ( op : unknown ) : string | null {
67+ if ( typeof op !== 'string' ) return null ;
68+ const lower = op . toLowerCase ( ) ;
69+ return FILTER_OPERATOR_ALIASES [ lower ] ?? FILTER_OPERATOR_ALIASES [ op ] ?? op ;
70+ }
71+
72+ function objectFilterEntryToAST ( entry : any ) : [ string , string , any ] | null {
73+ if ( ! entry || typeof entry !== 'object' ) return null ;
74+ const field = entry . field ?? entry . name ;
75+ const rawOp = entry . operator ?? entry . op ?? '=' ;
76+ const op = normalizeFilterOperator ( rawOp ) ;
77+ if ( ! field || ! op ) return null ;
78+ return [ String ( field ) , op , entry . value ] ;
79+ }
80+
2181// Module-level discovery cache. Multiple ObjectStackAdapter instances pointed
2282// at the same baseUrl (e.g. ConditionalAuthWrapper's throwaway adapter +
2383// AdapterProvider's main adapter) would otherwise each fire `/discovery`. By
@@ -749,8 +809,33 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
749809 : typeof params . $filter === 'object' && Object . keys ( params . $filter ) . length === 0 ;
750810 if ( ! isEmpty ) {
751811 if ( Array . isArray ( params . $filter ) ) {
752- // Assume active AST format if it's already an array
753- options . filters = params . $filter ;
812+ // Two array shapes are accepted from upstream:
813+ // 1. AST tuples: [field, op, value] — pass through.
814+ // 2. Object form: [{ field, operator, value }, ...] — server-driven
815+ // view configs (lead.view.ts etc.) use this. Translate each
816+ // entry into the AST tuple shape and map human-readable
817+ // operator names (`greater_than_or_equal`, `in`, `contains`,
818+ // …) to the canonical symbols the server understands.
819+ const isObjectForm = params . $filter . length > 0
820+ && typeof params . $filter [ 0 ] === 'object'
821+ && ! Array . isArray ( params . $filter [ 0 ] )
822+ && ( params . $filter [ 0 ] as any ) . field !== undefined ;
823+ if ( isObjectForm ) {
824+ const tuples = ( params . $filter as any [ ] )
825+ . map ( entry => objectFilterEntryToAST ( entry ) )
826+ . filter ( ( t ) : t is [ string , string , any ] => t !== null ) ;
827+ if ( tuples . length === 0 ) {
828+ // All entries were unrecognized — drop the filter rather than
829+ // sending a malformed array.
830+ } else if ( tuples . length === 1 ) {
831+ options . filters = tuples [ 0 ] ;
832+ } else {
833+ options . filters = [ 'and' , ...tuples ] ;
834+ }
835+ } else {
836+ // Already in AST format
837+ options . filters = params . $filter ;
838+ }
754839 } else {
755840 options . filters = convertFiltersToAST ( params . $filter ) ;
756841 }
0 commit comments