@object-ui/data-objectstack 4.0.11 → 4.2.1

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/CHANGELOG.md CHANGED
@@ -1,5 +1,33 @@
1
1
  # @object-ui/data-objectstack
2
2
 
3
+ ## 4.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - @object-ui/types@4.2.1
8
+ - @object-ui/core@4.2.1
9
+
10
+ ## 4.2.0
11
+
12
+ ### Patch Changes
13
+
14
+ - @object-ui/types@4.2.0
15
+ - @object-ui/core@4.2.0
16
+
17
+ ## 4.1.0
18
+
19
+ ### Patch Changes
20
+
21
+ - @object-ui/types@4.1.0
22
+ - @object-ui/core@4.1.0
23
+
24
+ ## 4.0.12
25
+
26
+ ### Patch Changes
27
+
28
+ - @object-ui/types@4.0.12
29
+ - @object-ui/core@4.0.12
30
+
3
31
  ## 4.0.11
4
32
 
5
33
  ### Patch Changes
package/dist/index.cjs CHANGED
@@ -881,6 +881,26 @@ function objectFilterEntryToAST(entry) {
881
881
  if (!field || !op) return null;
882
882
  return [String(field), op, entry.value];
883
883
  }
884
+ function translateFilterToAST(filter) {
885
+ if (filter === void 0 || filter === null) return void 0;
886
+ if (Array.isArray(filter)) {
887
+ if (filter.length === 0) return void 0;
888
+ const first = filter[0];
889
+ const isObjectForm = filter.length > 0 && typeof first === "object" && first !== null && !Array.isArray(first) && first.field !== void 0;
890
+ if (isObjectForm) {
891
+ const tuples = filter.map((entry) => objectFilterEntryToAST(entry)).filter((t) => t !== null);
892
+ if (tuples.length === 0) return void 0;
893
+ if (tuples.length === 1) return tuples[0];
894
+ return ["and", ...tuples];
895
+ }
896
+ return filter;
897
+ }
898
+ if (typeof filter === "object") {
899
+ if (Object.keys(filter).length === 0) return void 0;
900
+ return filter;
901
+ }
902
+ return void 0;
903
+ }
884
904
  var discoveryCache = /* @__PURE__ */ new Map();
885
905
  async function getSharedDiscovery(baseUrl, fetcher) {
886
906
  const key = baseUrl || "<default>";
@@ -1387,9 +1407,9 @@ var ObjectStackAdapter = class {
1387
1407
  }
1388
1408
  }
1389
1409
  if (params.$filter !== void 0 && params.$filter !== null) {
1390
- const isEmpty = Array.isArray(params.$filter) ? params.$filter.length === 0 : typeof params.$filter === "object" && Object.keys(params.$filter).length === 0;
1391
- if (!isEmpty) {
1392
- queryParams.set("filter", JSON.stringify(params.$filter));
1410
+ const translated = translateFilterToAST(params.$filter);
1411
+ if (translated !== void 0) {
1412
+ queryParams.set("filter", JSON.stringify(translated));
1393
1413
  }
1394
1414
  }
1395
1415
  const baseUrl = this.baseUrl.replace(/\/$/, "");
@@ -1802,6 +1822,19 @@ var ObjectStackAdapter = class {
1802
1822
  */
1803
1823
  async aggregate(resource, params) {
1804
1824
  await this.connect();
1825
+ const looksLikeSpecShape = params != null && (Array.isArray(params.groupBy) || Array.isArray(params.aggregations) || params.where !== void 0);
1826
+ if (looksLikeSpecShape) {
1827
+ const queryAst = {};
1828
+ if (Array.isArray(params.groupBy)) queryAst.groupBy = params.groupBy;
1829
+ if (Array.isArray(params.aggregations)) queryAst.aggregations = params.aggregations;
1830
+ if (params.where !== void 0) queryAst.where = params.where;
1831
+ if (typeof params.limit === "number") queryAst.limit = params.limit;
1832
+ const result = await this.client.data.query(resource, queryAst);
1833
+ if (Array.isArray(result)) return result;
1834
+ if (Array.isArray(result?.records)) return result.records;
1835
+ if (Array.isArray(result?.data)) return result.data;
1836
+ return [];
1837
+ }
1805
1838
  try {
1806
1839
  const measureName = params.function === "count" ? "count" : `${params.field}_${params.function}`;
1807
1840
  const payload = {
package/dist/index.d.cts CHANGED
@@ -968,12 +968,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
968
968
  * Falls back to client-side aggregation via find() if the analytics endpoint
969
969
  * is not available.
970
970
  */
971
- aggregate(resource: string, params: {
972
- field: string;
973
- function: string;
974
- groupBy: string;
975
- filter?: any;
976
- }): Promise<any[]>;
971
+ aggregate(resource: string, params: any): Promise<any[]>;
977
972
  /** Client-side aggregation fallback */
978
973
  private aggregateClientSide;
979
974
  /**
package/dist/index.d.ts CHANGED
@@ -968,12 +968,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
968
968
  * Falls back to client-side aggregation via find() if the analytics endpoint
969
969
  * is not available.
970
970
  */
971
- aggregate(resource: string, params: {
972
- field: string;
973
- function: string;
974
- groupBy: string;
975
- filter?: any;
976
- }): Promise<any[]>;
971
+ aggregate(resource: string, params: any): Promise<any[]>;
977
972
  /** Client-side aggregation fallback */
978
973
  private aggregateClientSide;
979
974
  /**
package/dist/index.js CHANGED
@@ -838,6 +838,26 @@ function objectFilterEntryToAST(entry) {
838
838
  if (!field || !op) return null;
839
839
  return [String(field), op, entry.value];
840
840
  }
841
+ function translateFilterToAST(filter) {
842
+ if (filter === void 0 || filter === null) return void 0;
843
+ if (Array.isArray(filter)) {
844
+ if (filter.length === 0) return void 0;
845
+ const first = filter[0];
846
+ const isObjectForm = filter.length > 0 && typeof first === "object" && first !== null && !Array.isArray(first) && first.field !== void 0;
847
+ if (isObjectForm) {
848
+ const tuples = filter.map((entry) => objectFilterEntryToAST(entry)).filter((t) => t !== null);
849
+ if (tuples.length === 0) return void 0;
850
+ if (tuples.length === 1) return tuples[0];
851
+ return ["and", ...tuples];
852
+ }
853
+ return filter;
854
+ }
855
+ if (typeof filter === "object") {
856
+ if (Object.keys(filter).length === 0) return void 0;
857
+ return filter;
858
+ }
859
+ return void 0;
860
+ }
841
861
  var discoveryCache = /* @__PURE__ */ new Map();
842
862
  async function getSharedDiscovery(baseUrl, fetcher) {
843
863
  const key = baseUrl || "<default>";
@@ -1344,9 +1364,9 @@ var ObjectStackAdapter = class {
1344
1364
  }
1345
1365
  }
1346
1366
  if (params.$filter !== void 0 && params.$filter !== null) {
1347
- const isEmpty = Array.isArray(params.$filter) ? params.$filter.length === 0 : typeof params.$filter === "object" && Object.keys(params.$filter).length === 0;
1348
- if (!isEmpty) {
1349
- queryParams.set("filter", JSON.stringify(params.$filter));
1367
+ const translated = translateFilterToAST(params.$filter);
1368
+ if (translated !== void 0) {
1369
+ queryParams.set("filter", JSON.stringify(translated));
1350
1370
  }
1351
1371
  }
1352
1372
  const baseUrl = this.baseUrl.replace(/\/$/, "");
@@ -1759,6 +1779,19 @@ var ObjectStackAdapter = class {
1759
1779
  */
1760
1780
  async aggregate(resource, params) {
1761
1781
  await this.connect();
1782
+ const looksLikeSpecShape = params != null && (Array.isArray(params.groupBy) || Array.isArray(params.aggregations) || params.where !== void 0);
1783
+ if (looksLikeSpecShape) {
1784
+ const queryAst = {};
1785
+ if (Array.isArray(params.groupBy)) queryAst.groupBy = params.groupBy;
1786
+ if (Array.isArray(params.aggregations)) queryAst.aggregations = params.aggregations;
1787
+ if (params.where !== void 0) queryAst.where = params.where;
1788
+ if (typeof params.limit === "number") queryAst.limit = params.limit;
1789
+ const result = await this.client.data.query(resource, queryAst);
1790
+ if (Array.isArray(result)) return result;
1791
+ if (Array.isArray(result?.records)) return result.records;
1792
+ if (Array.isArray(result?.data)) return result.data;
1793
+ return [];
1794
+ }
1762
1795
  try {
1763
1796
  const measureName = params.function === "count" ? "count" : `${params.field}_${params.function}`;
1764
1797
  const payload = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@object-ui/data-objectstack",
3
- "version": "4.0.11",
3
+ "version": "4.2.1",
4
4
  "description": "ObjectStack Data Adapter for Object UI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -23,8 +23,8 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "@objectstack/client": "^4.0.5",
26
- "@object-ui/core": "4.0.11",
27
- "@object-ui/types": "4.0.11"
26
+ "@object-ui/core": "4.2.1",
27
+ "@object-ui/types": "4.2.1"
28
28
  },
29
29
  "devDependencies": {
30
30
  "tsup": "^8.5.1",
package/src/index.ts CHANGED
@@ -78,6 +78,54 @@ function objectFilterEntryToAST(entry: any): [string, string, any] | null {
78
78
  return [String(field), op, entry.value];
79
79
  }
80
80
 
81
+ /**
82
+ * Translate any of the filter shapes accepted by ObjectUI into the AST format
83
+ * understood by the ObjectStack server's `parseFilterAST()`.
84
+ *
85
+ * Accepted inputs:
86
+ * - `[{ field, operator, value }, ...]` — ViewFilterRule[] from view configs
87
+ * - `[field, op, value]` — single AST tuple (passed through)
88
+ * - `['and'|'or', ...children]` — logical AST node (passed through)
89
+ * - `[[...], [...]]` — legacy nested AST (passed through)
90
+ * - `{ field: value }` / `{ field: { $op: value } }` — MongoDB-style object
91
+ *
92
+ * Returns `undefined` when the input is empty/unrecognized so callers can
93
+ * skip emitting `?filter=` entirely.
94
+ */
95
+ function translateFilterToAST(filter: unknown): unknown | undefined {
96
+ if (filter === undefined || filter === null) return undefined;
97
+
98
+ if (Array.isArray(filter)) {
99
+ if (filter.length === 0) return undefined;
100
+
101
+ // Object form: [{ field, operator, value }, ...]
102
+ const first = filter[0];
103
+ const isObjectForm = filter.length > 0
104
+ && typeof first === 'object'
105
+ && first !== null
106
+ && !Array.isArray(first)
107
+ && (first as any).field !== undefined;
108
+ if (isObjectForm) {
109
+ const tuples = (filter as any[])
110
+ .map(entry => objectFilterEntryToAST(entry))
111
+ .filter((t): t is [string, string, any] => t !== null);
112
+ if (tuples.length === 0) return undefined;
113
+ if (tuples.length === 1) return tuples[0];
114
+ return ['and', ...tuples];
115
+ }
116
+
117
+ // Already AST — pass through.
118
+ return filter;
119
+ }
120
+
121
+ if (typeof filter === 'object') {
122
+ if (Object.keys(filter as Record<string, unknown>).length === 0) return undefined;
123
+ return filter;
124
+ }
125
+
126
+ return undefined;
127
+ }
128
+
81
129
  // Module-level discovery cache. Multiple ObjectStackAdapter instances pointed
82
130
  // at the same baseUrl (e.g. ConditionalAuthWrapper's throwaway adapter +
83
131
  // AdapterProvider's main adapter) would otherwise each fire `/discovery`. By
@@ -844,13 +892,15 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
844
892
  }
845
893
  }
846
894
 
847
- // Filter — drop empty arrays/objects so we don't send `?filter=%5B%5D`
895
+ // Filter — translate ViewFilterRule[] (`[{field, operator, value}]`)
896
+ // and other shapes into AST tuples the server understands. Without this,
897
+ // server-driven views (e.g. `at_risk_accounts`, `hot_leads`) ship raw
898
+ // `[{field,operator,value}]` arrays which `parseFilterAST` silently
899
+ // discards, returning every record instead of the filtered subset.
848
900
  if (params.$filter !== undefined && params.$filter !== null) {
849
- const isEmpty = Array.isArray(params.$filter)
850
- ? params.$filter.length === 0
851
- : typeof params.$filter === 'object' && Object.keys(params.$filter).length === 0;
852
- if (!isEmpty) {
853
- queryParams.set('filter', JSON.stringify(params.$filter));
901
+ const translated = translateFilterToAST(params.$filter);
902
+ if (translated !== undefined) {
903
+ queryParams.set('filter', JSON.stringify(translated));
854
904
  }
855
905
  }
856
906
 
@@ -1384,9 +1434,32 @@ export class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1384
1434
  * Falls back to client-side aggregation via find() if the analytics endpoint
1385
1435
  * is not available.
1386
1436
  */
1387
- async aggregate(resource: string, params: { field: string; function: string; groupBy: string; filter?: any }): Promise<any[]> {
1437
+ async aggregate(resource: string, params: any): Promise<any[]> {
1388
1438
  await this.connect();
1389
1439
 
1440
+ // Spec-shape aggregation: `{ groupBy: GroupByNode[], aggregations: AggregationNode[], where?, limit? }`
1441
+ // per spec/data/query.zod.ts. Sent directly to the server's POST
1442
+ // /data/:object/query endpoint, which routes through engine.aggregate
1443
+ // and returns bucketed rows with the requested aliases.
1444
+ const looksLikeSpecShape =
1445
+ params != null &&
1446
+ (Array.isArray((params as any).groupBy) ||
1447
+ Array.isArray((params as any).aggregations) ||
1448
+ (params as any).where !== undefined);
1449
+ if (looksLikeSpecShape) {
1450
+ const queryAst: Record<string, unknown> = {};
1451
+ if (Array.isArray(params.groupBy)) queryAst.groupBy = params.groupBy;
1452
+ if (Array.isArray(params.aggregations)) queryAst.aggregations = params.aggregations;
1453
+ if (params.where !== undefined) queryAst.where = params.where;
1454
+ if (typeof params.limit === 'number') queryAst.limit = params.limit;
1455
+ const result: any = await this.client.data.query(resource, queryAst as any);
1456
+ // client.data.query returns { object, records, total, hasMore }
1457
+ if (Array.isArray(result)) return result;
1458
+ if (Array.isArray(result?.records)) return result.records;
1459
+ if (Array.isArray(result?.data)) return result.data;
1460
+ return [];
1461
+ }
1462
+
1390
1463
  try {
1391
1464
  // Build measure name in the format expected by the backend analytics
1392
1465
  // service (memory-analytics / cube). For 'count' the measure key is