@objectstack/client 3.0.9 → 3.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
2
2
 
3
- import { QueryAST, SortNode, AggregationNode } from '@objectstack/spec/data';
3
+ import { QueryAST, SortNode, AggregationNode, isFilterAST } from '@objectstack/spec/data';
4
4
  import {
5
5
  BatchUpdateRequest,
6
6
  BatchUpdateResponse,
@@ -115,7 +115,10 @@ export type DiscoveryResult = GetDiscoveryResponse;
115
115
 
116
116
  export interface QueryOptions {
117
117
  select?: string[]; // Simplified Selection
118
- filters?: Record<string, any>; // Map or AST
118
+ /** @canonical Preferred filter parameter (singular). */
119
+ filter?: Record<string, any> | unknown[]; // Map or AST
120
+ /** @deprecated Use `filter` (singular). Kept for backward compatibility. */
121
+ filters?: Record<string, any> | unknown[]; // Map or AST
119
122
  sort?: string | string[] | SortNode[]; // 'name' or ['-created_at'] or AST
120
123
  top?: number;
121
124
  skip?: number;
@@ -1445,14 +1448,19 @@ export class ObjectStackClient {
1445
1448
  }
1446
1449
 
1447
1450
  // 4. Handle Filters (Simple vs AST)
1448
- if (options.filters) {
1451
+ // Canonical HTTP param name: `filter` (singular). `filters` (plural) is accepted
1452
+ // for backward compatibility but `filter` is the standard going forward.
1453
+ const filterValue = options.filter ?? options.filters;
1454
+ if (filterValue) {
1449
1455
  // Detect AST filter format vs simple key-value map. AST filters use an array structure
1450
- // with [field, operator, value] or [logicOp, ...nodes] shape (see isFilterAST).
1456
+ // with [field, operator, value] or [logicOp, ...nodes] shape (see isFilterAST from spec).
1451
1457
  // For complex filter expressions, use .query() which builds a proper QueryAST.
1452
- if (this.isFilterAST(options.filters)) {
1453
- queryParams.set('filters', JSON.stringify(options.filters));
1454
- } else {
1455
- Object.entries(options.filters).forEach(([k, v]) => {
1458
+ if (this.isFilterAST(filterValue) || Array.isArray(filterValue)) {
1459
+ // AST or any array → serialize as JSON in `filter` param
1460
+ queryParams.set('filter', JSON.stringify(filterValue));
1461
+ } else if (typeof filterValue === 'object' && filterValue !== null) {
1462
+ // Plain key-value map → append each as individual query params
1463
+ Object.entries(filterValue as Record<string, unknown>).forEach(([k, v]) => {
1456
1464
  if (v !== undefined && v !== null) {
1457
1465
  queryParams.append(k, String(v));
1458
1466
  }
@@ -1571,10 +1579,9 @@ export class ObjectStackClient {
1571
1579
  */
1572
1580
 
1573
1581
  private isFilterAST(filter: any): boolean {
1574
- // Basic check: if array, it's [field, op, val] or [logic, node, node]
1575
- // If object but not basic KV map... harder to tell without schema
1576
- // For now, assume if it passes Array.isArray it's an AST root
1577
- return Array.isArray(filter);
1582
+ // Delegate to the spec-exported structural validator instead of naive Array.isArray.
1583
+ // This checks for valid AST shapes: [field, op, val], [logic, ...nodes], or [[cond], ...].
1584
+ return isFilterAST(filter);
1578
1585
  }
1579
1586
 
1580
1587
  /**