@object-ui/data-objectstack 16.1.0 → 17.2.0

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/dist/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { ObjectStackClient } from '@objectstack/client';
2
- import { DataSource, QueryParams, QueryResult, MutationEvent, BatchTransactionOperation, ImportRequestOptions, ImportRecordsResult, CreateImportJobResult, ImportJobProgressInfo, ImportJobResultsInfo, ListImportJobsOptions, ImportJobSummaryInfo, ImportJobUndoResult, ExportDownloadRequest, FileUploadResult } from '@object-ui/types';
2
+ import { DroppedFieldsEvent } from '@objectstack/spec/data';
3
+ export { DroppedFieldsEvent } from '@objectstack/spec/data';
4
+ import { DataSource, QueryParams, QueryResult, GlobalSearchResult, DataSourceMutationEvent, BatchTransactionOperation, ImportRequestOptions, ImportRecordsResult, CreateImportJobResult, ImportJobProgressInfo, ImportJobResultsInfo, ListImportJobsOptions, ImportJobSummaryInfo, ImportJobUndoResult, ExportDownloadRequest, FileUploadResult } from '@object-ui/types';
3
5
  export { FileUploadResult } from '@object-ui/types';
4
6
 
5
7
  /**
@@ -95,16 +97,28 @@ declare class AuthenticationError extends ObjectStackError {
95
97
  constructor(message?: string, details?: Record<string, unknown>, statusCode?: number);
96
98
  }
97
99
  /**
98
- * Error thrown when data validation fails
100
+ * Error thrown when the ObjectStack data API rejects a write as invalid.
101
+ *
102
+ * NOT the spec's `ValidationError` (`@objectstack/spec/kernel`), whose name this
103
+ * class wore until objectui#3160 (objectstack#4115 ledger batch 6). That one is
104
+ * a plain DATA SHAPE — `{ field, message, code? }`, one entry in a plugin
105
+ * manifest's validation report — and `@object-ui/types` re-exports it under that
106
+ * name. This is a runtime `Error` subclass carrying an HTTP status plus a list
107
+ * of such entries, so the two are not even the same KIND of thing.
108
+ *
109
+ * The name follows the convention registered on objectstack#4115 for this
110
+ * family — `<what was validated>Validation<Error|Result>`. `@object-ui/core`
111
+ * took `SchemaNodeValidationError` for its SDUI-tree walk; this one belongs to
112
+ * the data API.
99
113
  */
100
- declare class ValidationError extends ObjectStackError {
114
+ declare class DataApiValidationError extends ObjectStackError {
101
115
  field?: string | undefined;
102
116
  validationErrors?: Array<{
103
117
  field: string;
104
118
  message: string;
105
119
  }> | undefined;
106
120
  /**
107
- * Create a new ValidationError
121
+ * Create a new DataApiValidationError
108
122
  *
109
123
  * @param message - Human-readable error message
110
124
  * @param field - The field that failed validation (optional)
@@ -148,9 +162,18 @@ declare function isErrorType<T extends ObjectStackError>(error: unknown, errorCl
148
162
  * LICENSE file in the root directory of this source tree.
149
163
  */
150
164
  /**
151
- * Cache statistics for monitoring
165
+ * Statistics reported by {@link MetadataCache.getStats} for monitoring.
166
+ *
167
+ * NOT the spec's `CacheStats` (`@objectstack/spec/contracts`), whose name this
168
+ * interface wore until objectui#3160 (objectstack#4115 ledger batch 6). That one
169
+ * describes the platform's `ICacheService` — a server-side KV cache measured by
170
+ * `keyCount` and `memoryUsage`. This one describes the browser-side LRU in front
171
+ * of `/api/v1/meta/*`: it is bounded (`size`/`maxSize`), it evicts, it coalesces
172
+ * concurrent fetches onto one in-flight promise, and it reports a `hitRate`.
173
+ * Neither type has a key the other has, so this is a name collision, not a
174
+ * dialect — deriving would have replaced every field.
152
175
  */
153
- interface CacheStats {
176
+ interface MetadataCacheStats {
154
177
  size: number;
155
178
  maxSize: number;
156
179
  hits: number;
@@ -563,7 +586,21 @@ interface MetadataDraftHeader {
563
586
  updatedAt: string | null;
564
587
  updatedBy: string | null;
565
588
  }
566
- interface MetadataSaveOptions {
589
+ /**
590
+ * Options for {@link MetadataClient.save} — a WRITE OVER HTTP to
591
+ * `/api/v1/meta/:type/:name`.
592
+ *
593
+ * NOT the spec's `MetadataSaveOptions` (`@objectstack/spec/system` and
594
+ * `/kernel`), whose name this interface wore until objectui#3160
595
+ * (objectstack#4115 ledger batch 6). Both spec copies describe writing a
596
+ * metadata item to a FILE — `format: json|yaml|ts`, `path`, `indent`,
597
+ * `prettify`, `sortKeys`, `backup`, `atomic`, `loader`. Not one of those keys
598
+ * exists here, and not one of these exists there: this is the REST client's
599
+ * request envelope — optimistic concurrency (`ifMatch` → `If-Match`), actor
600
+ * attribution, the destructive-change override, the ADR-0033 draft/publish
601
+ * mode, and the owning package. Same words, different layer.
602
+ */
603
+ interface MetadataClientSaveOptions {
567
604
  /**
568
605
  * Optimistic concurrency token (the `checksum` returned by the last
569
606
  * read). When present, sent as the `If-Match` header so concurrent
@@ -608,7 +645,7 @@ interface MetadataGetOptions {
608
645
  */
609
646
  packageId?: string;
610
647
  }
611
- interface MetadataDeleteOptions extends MetadataSaveOptions {
648
+ interface MetadataDeleteOptions extends MetadataClientSaveOptions {
612
649
  /**
613
650
  * Target state. `'draft'` discards the pending draft (keeps the
614
651
  * published overlay intact). Omit to reset the active overlay back
@@ -847,7 +884,7 @@ declare class MetadataClient {
847
884
  * envelopes; we send bare for consistency. Pass `mode: 'draft'` to
848
885
  * stage the change without publishing (Studio's "Save" button).
849
886
  */
850
- save<T = unknown>(type: string, name: string, item: unknown, options?: MetadataSaveOptions): Promise<T>;
887
+ save<T = unknown>(type: string, name: string, item: unknown, options?: MetadataClientSaveOptions): Promise<T>;
851
888
  /**
852
889
  * Publish a single pending draft BY REFERENCE — promotes the draft row for
853
890
  * `(type, name)` into the active overlay and drops the draft. Works for ANY
@@ -957,7 +994,20 @@ declare class MetadataClient {
957
994
  * Security module integration for @objectstack/spec v3.0.0
958
995
  * Provides advanced security policies: CSP config, audit logging, data masking.
959
996
  */
960
- interface SecurityPolicy {
997
+ /**
998
+ * Configuration for {@link SecurityManager} — this adapter's browser-side
999
+ * security posture: the CSP header it generates, the in-memory audit log it
1000
+ * keeps, and the field-masking rules it applies before a record is rendered.
1001
+ *
1002
+ * NOT the spec's `SecurityPolicy` (`@objectstack/spec/kernel`), whose name this
1003
+ * interface wore until objectui#3160 (objectstack#4115 ledger batch 6). That one
1004
+ * is the PACKAGE SUPPLY-CHAIN policy — `{ id, name, autoScan, thresholds,
1005
+ * allowedLicenses, prohibitedLicenses, codeSigning, sandbox }` — the rules a
1006
+ * marketplace applies when scanning a plugin before install. It shares no key
1007
+ * with this one, so there was nothing to derive; the collision is the word
1008
+ * "security", not the concept.
1009
+ */
1010
+ interface SecurityManagerPolicy {
961
1011
  /** Content Security Policy configuration */
962
1012
  csp?: CSPConfig;
963
1013
  /** Audit logging configuration */
@@ -1031,7 +1081,7 @@ interface AuditLogEntry {
1031
1081
  declare class SecurityManager {
1032
1082
  private policy;
1033
1083
  private auditLog;
1034
- constructor(policy?: SecurityPolicy);
1084
+ constructor(policy?: SecurityManagerPolicy);
1035
1085
  /**
1036
1086
  * Generate a CSP header string from the configuration.
1037
1087
  */
@@ -1055,11 +1105,11 @@ declare class SecurityManager {
1055
1105
  /**
1056
1106
  * Update the security policy.
1057
1107
  */
1058
- updatePolicy(policy: Partial<SecurityPolicy>): void;
1108
+ updatePolicy(policy: Partial<SecurityManagerPolicy>): void;
1059
1109
  /**
1060
1110
  * Get current security policy.
1061
1111
  */
1062
- getPolicy(): SecurityPolicy;
1112
+ getPolicy(): SecurityManagerPolicy;
1063
1113
  }
1064
1114
 
1065
1115
  /**
@@ -1174,6 +1224,64 @@ declare function calculateAutoLayout(items: Array<{
1174
1224
  y: number;
1175
1225
  }>;
1176
1226
 
1227
+ /**
1228
+ * Map human-readable filter operator names produced by SDUI view configs
1229
+ * (e.g. `lead.view.ts`) to the canonical operator symbols expected by the
1230
+ * ObjectStack server's filter AST. Unknown operators fall through unchanged
1231
+ * so existing AST-style entries keep working.
1232
+ *
1233
+ * Every VALUE here must be a member of the spec's `VALID_AST_OPERATORS`
1234
+ * (`@objectstack/spec/data`) — that set gates `isFilterAST()`, and a filter it
1235
+ * rejects is not converted, not validated, and then silently DROPPED by
1236
+ * driver-sql (objectstack#3948). Pinned by `filter-operator-ast-parity.test.ts`.
1237
+ *
1238
+ * Exported for that test. @internal
1239
+ */
1240
+ declare const FILTER_OPERATOR_ALIASES: Record<string, string>;
1241
+ /**
1242
+ * A filter entry this adapter cannot translate into an AST tuple.
1243
+ *
1244
+ * Thrown rather than skipped. Dropping one entry out of an `and` WIDENS the
1245
+ * result set, and dropping the last one emits no `filter=` at all — every row,
1246
+ * no error, from a query that asked for a subset. That is the same silent
1247
+ * over-fetch the server-side drivers stopped doing in objectstack#3948, and
1248
+ * skipping it here just moves it one layer up.
1249
+ *
1250
+ * Carries the code and status the data API uses for its own version of this
1251
+ * refusal (objectstack#4121) so a failed list renders "this view's filter is
1252
+ * malformed" rather than "check your connection" (#3066).
1253
+ */
1254
+ declare class MalformedFilterError extends Error {
1255
+ readonly code = "INVALID_FILTER";
1256
+ readonly httpStatus = 400;
1257
+ readonly entry: unknown;
1258
+ readonly index: number;
1259
+ constructor(entry: unknown, index: number);
1260
+ }
1261
+ /** Detect the malformed-filter refusal, whether raised here or by the server. */
1262
+ declare function isMalformedFilterError(error: unknown): boolean;
1263
+ /**
1264
+ * Serialize a `$orderby` to the server's `sort` shorthand
1265
+ * (`field,-other_field`), for every shape `QueryParams['$orderby']` declares.
1266
+ *
1267
+ * The type declares four — `string`, `string[]`, `SortNode[]`,
1268
+ * `Record<field, direction>` — and the two `find()` routes each open-coded a
1269
+ * fold that handled three of them. The missing one was the bare string, and it
1270
+ * did not degrade quietly: `Object.entries('name asc')` enumerates a string's
1271
+ * character indices, so the request went out as `sort=0,1,2,3,4,5,6,7`. Against
1272
+ * a server that rejects an unreadable sort rather than ignoring it
1273
+ * (objectstack#4226), that is a `400 INVALID_SORT` and an empty list — so a
1274
+ * standalone `ObjectGrid` with a `sort` in its metadata, which is exactly the
1275
+ * shape it builds (`ObjectGrid.tsx`: `` `${field} ${order}` ``), failed to load
1276
+ * at all.
1277
+ *
1278
+ * One serializer for both routes, for the reason the filter path already has
1279
+ * one: two copies of a fold can only agree by inspection, and these two did not.
1280
+ *
1281
+ * Returns `undefined` when nothing is sortable, so callers skip the parameter
1282
+ * entirely rather than sending an empty one.
1283
+ */
1284
+ declare function serializeOrderBy(orderby: QueryParams['$orderby']): string | undefined;
1177
1285
  /**
1178
1286
  * Fetch the server `discovery` document once per (baseUrl) and reuse the
1179
1287
  * resulting Promise. Used by `ObjectStackAdapter.connect()` (and any caller
@@ -1212,6 +1320,73 @@ declare function readTransactionalBatchCapability(discovery: unknown): boolean |
1212
1320
  * tripping on the property-name mismatch.
1213
1321
  */
1214
1322
  declare function is404Error(error: unknown): boolean;
1323
+ /**
1324
+ * Thrown when the deployment has no analytics capability installed
1325
+ * (framework#3891 / #4019).
1326
+ *
1327
+ * The framework retired its degraded in-kernel analytics fallback — it dropped
1328
+ * the caller's RLS/tenant scope and ignored the contract filter, so it answered
1329
+ * 200 with over-broad numbers. `@objectstack/service-analytics` is now the
1330
+ * domain's only implementation, and a deployment without it answers:
1331
+ *
1332
+ * - `POST /api/v1/analytics/query` → **404** (the routes aren't even mounted);
1333
+ * - `POST /api/v1/analytics/dataset/query` → **501 NOT_IMPLEMENTED**.
1334
+ *
1335
+ * Neither is a bug to report as a stack trace: it is a deployment that hasn't
1336
+ * installed the capability. This error carries a message a UI can show as-is.
1337
+ */
1338
+ declare class AnalyticsNotInstalledError extends Error {
1339
+ readonly code = "ANALYTICS_NOT_INSTALLED";
1340
+ /** The surface that was unavailable, for the message a host renders. */
1341
+ readonly surface: string;
1342
+ constructor(surface: string, detail?: string);
1343
+ }
1344
+ /** True when `error` is an {@link AnalyticsNotInstalledError} (or its wire twin). */
1345
+ declare function isAnalyticsNotInstalledError(error: unknown): boolean;
1346
+ /**
1347
+ * Thrown when the server REJECTED the analytics query body (HTTP 400 —
1348
+ * `VALIDATION_FAILED` since framework#4010 validates `/analytics/query` at the
1349
+ * entry against the canonical bare `AnalyticsQuery` shape).
1350
+ *
1351
+ * Distinct from {@link AnalyticsNotInstalledError} on purpose: this one is a
1352
+ * defect in what WE sent, so it must never be answered with the client-side
1353
+ * fallback. Numbers produced by a different code path would look plausible and
1354
+ * bury the contract violation — the misdirection framework#3878 documented.
1355
+ */
1356
+ declare class AnalyticsQueryRejectedError extends Error {
1357
+ readonly code = "ANALYTICS_QUERY_REJECTED";
1358
+ /** The server's own error code (e.g. `VALIDATION_FAILED`), when it sent one. */
1359
+ readonly serverCode?: string;
1360
+ constructor(detail?: string, serverCode?: string);
1361
+ }
1362
+ /**
1363
+ * Classify a FAILED analytics call so the caller knows whether to degrade or
1364
+ * to surface the failure.
1365
+ *
1366
+ * `@objectstack/client`'s fetch wrapper throws on a non-2xx, decorating the
1367
+ * error with the semantic `code` string and the numeric `httpStatus` (the
1368
+ * ADR-0112 / framework#3842 shape this repo already reads elsewhere). Two
1369
+ * outcomes must NOT be conflated:
1370
+ *
1371
+ * - **`not-installed`** — the deployment has no analytics service. Since
1372
+ * framework#3891 retired the degraded in-kernel shim, that is a 404 (the
1373
+ * routes aren't mounted at all, framework#4019) or a 501 `NOT_IMPLEMENTED`
1374
+ * (REST's dataset route with no service behind it). Degrading to a
1375
+ * client-side aggregate over a scoped `find()` is CORRECT here.
1376
+ * - **`rejected`** — the server refused OUR body (400 `VALIDATION_FAILED`;
1377
+ * framework#4010 validates `/analytics/query` at the entry). Degrading
1378
+ * would answer our own contract violation with plausible numbers from a
1379
+ * different code path and bury it — the misdirection framework#3878
1380
+ * documented. It must be surfaced.
1381
+ *
1382
+ * Anything else (5xx, network) is `unknown`: degrade, but silently — it is a
1383
+ * transient failure, not a deployment that is missing a capability.
1384
+ */
1385
+ declare function classifyAnalyticsFailure(error: unknown): {
1386
+ kind: 'not-installed' | 'rejected' | 'unknown';
1387
+ code?: string;
1388
+ message?: string;
1389
+ };
1215
1390
  /**
1216
1391
  * Thrown by `update()` / `delete()` when the server returns
1217
1392
  * `409 CONCURRENT_UPDATE` — i.e. the record was modified by someone else
@@ -1248,10 +1423,15 @@ declare class ConcurrentUpdateError extends Error {
1248
1423
  */
1249
1424
  declare function isConcurrentUpdateError(error: unknown): error is ConcurrentUpdateError;
1250
1425
  /**
1251
- * Convert any error thrown by the upstream client into a typed
1252
- * `ConcurrentUpdateError` when it represents a 409 CONCURRENT_UPDATE.
1253
- * Returns the original error untouched otherwise. Callers can simply
1254
- * `throw normaliseClientError(err)` from their catch blocks.
1426
+ * Convert any error thrown by the upstream client into a typed error when we
1427
+ * recognise its shape. Returns the original error untouched otherwise, so
1428
+ * callers can simply `throw normaliseClientError(err)` from their catch blocks.
1429
+ *
1430
+ * Two shapes are recognised:
1431
+ * - `409` + `CONCURRENT_UPDATE` → {@link ConcurrentUpdateError};
1432
+ * - `400` + `VALIDATION_FAILED` → {@link DataApiValidationError}, carrying the
1433
+ * server's per-field entries so a form can mark the offending inputs
1434
+ * instead of showing one undirected toast.
1255
1435
  */
1256
1436
  declare function normaliseClientError(error: unknown): unknown;
1257
1437
  /**
@@ -1285,6 +1465,21 @@ type ConnectionStateListener = (event: ConnectionStateEvent) => void;
1285
1465
  */
1286
1466
  type BatchProgressListener = (event: BatchProgressEvent) => void;
1287
1467
 
1468
+ /**
1469
+ * Emitted after a create/update whose response carried `droppedFields`
1470
+ * (framework #3431/#3455). The write SUCCEEDED — this is a warning that some
1471
+ * supplied fields never landed, so the UI can tell the user rather than let it
1472
+ * pass silently. Subscribe via {@link ObjectStackAdapter.onWriteWarning}.
1473
+ */
1474
+ interface WriteWarningEvent {
1475
+ operation: 'create' | 'update';
1476
+ resource: string;
1477
+ id?: string | number;
1478
+ droppedFields: DroppedFieldsEvent[];
1479
+ }
1480
+ /** Event listener type for write-warning (dropped-fields) events. */
1481
+ type WriteWarningListener = (event: WriteWarningEvent) => void;
1482
+
1288
1483
  /**
1289
1484
  * ObjectStack Data Source Adapter
1290
1485
  *
@@ -1328,12 +1523,15 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1328
1523
  private reconnectAttempts;
1329
1524
  private baseUrl;
1330
1525
  private token?;
1526
+ /** One "analytics capability is missing" console line per adapter, not per widget. */
1527
+ private analyticsCapabilityWarned;
1331
1528
  private fetchImpl;
1332
1529
  private inflightFinds;
1333
1530
  private missingResources;
1334
1531
  private batchUnsupported;
1335
1532
  private atomicBatchCapability;
1336
1533
  private mutationListeners;
1534
+ private writeWarningListeners;
1337
1535
  constructor(config: {
1338
1536
  baseUrl: string;
1339
1537
  token?: string;
@@ -1384,6 +1582,26 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1384
1582
  * Converts OData-style params to ObjectStack query options.
1385
1583
  */
1386
1584
  find(resource: string, params?: QueryParams): Promise<QueryResult<T>>;
1585
+ /**
1586
+ * Full-text search across every searchable object in a single round-trip.
1587
+ *
1588
+ * Hits `GET /api/v1/search?q=`, the platform's global search endpoint served
1589
+ * by the registered search service (the pinyin full-text plugin) and backed
1590
+ * by `metadata-protocol`'s `searchAll`. Unlike `find(resource, { $search })`
1591
+ * — a per-object metadata-driven search (ADR-0061) — this consults the search
1592
+ * index and ranks hits across objects, so it surfaces records the per-object
1593
+ * fanout misses. Global affordances (⌘K command palette, search page) prefer
1594
+ * this path (framework #3371).
1595
+ *
1596
+ * Returns `{ query, hits }`. A backend without the search plugin installed
1597
+ * answers `404`; we treat that as "no global search here" and return an empty
1598
+ * hit set so callers can fall back to a per-object fanout rather than surface
1599
+ * an error.
1600
+ */
1601
+ searchAll(query: string, options?: {
1602
+ limit?: number;
1603
+ objects?: string[];
1604
+ }): Promise<GlobalSearchResult>;
1387
1605
  /**
1388
1606
  * Find a single record by ID.
1389
1607
  */
@@ -1402,7 +1620,38 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1402
1620
  * mutation (e.g. inline-edit "Save All", which writes through `update` and
1403
1621
  * must repaint the list without a manual reload).
1404
1622
  */
1405
- onMutation(callback: (event: MutationEvent<T>) => void): () => void;
1623
+ onMutation(callback: (event: DataSourceMutationEvent<T>) => void): () => void;
1624
+ /**
1625
+ * Notify all write-warning subscribers. Isolated like {@link emitMutation}: a
1626
+ * throwing listener must not break the write or starve the others.
1627
+ */
1628
+ private emitWriteWarning;
1629
+ /**
1630
+ * Read `droppedFields` off a create/update response (framework #3431/#3455)
1631
+ * and, when present, notify write-warning subscribers. Tolerant of a client
1632
+ * whose response type predates `droppedFields`: the field is read structurally
1633
+ * and validated, so an older client (or a backend that never drops) is a no-op.
1634
+ */
1635
+ private notifyDroppedFields;
1636
+ /**
1637
+ * Same, for the cross-object transactional batch (framework #3794). Its
1638
+ * response hangs the events off a top-level `droppedFields` list, each tagged
1639
+ * with the `index` of the operation it came from — `results` entries are bare
1640
+ * record echoes with nowhere to hang a per-row list.
1641
+ *
1642
+ * This is the path that matters most for the warning: `batchTransaction` is
1643
+ * how the console's record form saves a master-detail record, so a
1644
+ * `readonlyWhen`-locked field edited in that form was stripped server-side
1645
+ * while the UI reported a plain success. The operation kind is taken from the
1646
+ * originating op so the toast doesn't call an update a create.
1647
+ */
1648
+ private notifyBatchDroppedFields;
1649
+ /**
1650
+ * Subscribe to write-warning events (a create/update dropped caller-supplied
1651
+ * fields — #3431/#3455). Returns an unsubscribe function. The app shell uses
1652
+ * this to toast the user; the write itself already succeeded.
1653
+ */
1654
+ onWriteWarning(callback: WriteWarningListener): () => void;
1406
1655
  create(resource: string, data: Partial<T>): Promise<T>;
1407
1656
  /**
1408
1657
  * Update an existing record.
@@ -1498,7 +1747,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1498
1747
  /** Mark the endpoint unsupported (warn once) and serve via emulation. */
1499
1748
  private fallbackToEmulation;
1500
1749
  /**
1501
- * Emit one MutationEvent per committed operation so the invalidation bus
1750
+ * Emit one DataSourceMutationEvent per committed operation so the invalidation bus
1502
1751
  * (#2269) sees writes that went through /batch exactly like single
1503
1752
  * create/update/delete calls — master-detail ModalForm saves otherwise leave
1504
1753
  * related lists and count badges stale (#2582). `results` is index-aligned
@@ -1776,6 +2025,23 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1776
2025
  * is not available.
1777
2026
  */
1778
2027
  aggregate(resource: string, params: any): Promise<any[]>;
2028
+ /**
2029
+ * Client-side aggregation over a server-scoped `find()` — the fallback used
2030
+ * whenever the analytics endpoint cannot answer (capability absent, network
2031
+ * failure, or a result whose measure came back missing).
2032
+ *
2033
+ * Forwarding `params.filter` is load-bearing: without it the fallback
2034
+ * aggregates the whole table while the caller believes it applied a filter,
2035
+ * which is the "KPI silently sums everything" failure this adapter has
2036
+ * guarded against since the widget filter was threaded through.
2037
+ */
2038
+ private aggregateViaFind;
2039
+ /**
2040
+ * Say "the analytics capability isn't installed" ONCE per adapter, not once
2041
+ * per widget: a dashboard fans out one aggregate() per KPI, and N identical
2042
+ * console lines read like N different failures.
2043
+ */
2044
+ private warnAnalyticsCapabilityOnce;
1779
2045
  /**
1780
2046
  * Run a semantic-layer `dataset` (ADR-0021) and return chart-ready rows.
1781
2047
  *
@@ -1836,6 +2102,13 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1836
2102
  }>;
1837
2103
  }>;
1838
2104
  /** Client-side aggregation fallback */
2105
+ /**
2106
+ * The result column an object-bound `aggregate` projects its value under
2107
+ * (framework#3701, `chartAggregateValueKey` in `@objectstack/spec/ui`): the
2108
+ * raw `field` name — no `sum_`-style decoration, unlike a dataset measure —
2109
+ * or the literal `count` when a count names no field.
2110
+ */
2111
+ private aggregateValueKey;
1839
2112
  private aggregateClientSide;
1840
2113
  /**
1841
2114
  * Get multiple metadata items from ObjectStack.
@@ -1850,7 +2123,7 @@ declare class ObjectStackAdapter<T = unknown> implements DataSource<T> {
1850
2123
  /**
1851
2124
  * Get cache statistics for monitoring performance.
1852
2125
  */
1853
- getCacheStats(): CacheStats;
2126
+ getCacheStats(): MetadataCacheStats;
1854
2127
  /**
1855
2128
  * Invalidate metadata cache entries.
1856
2129
  *
@@ -1938,4 +2211,4 @@ declare function createObjectStackAdapter<T = unknown>(config: {
1938
2211
  reconnectDelay?: number;
1939
2212
  }): DataSource<T>;
1940
2213
 
1941
- export { type AuditEventType, type AuditLogConfig, type AuditLogEntry, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, type CSPConfig, type CacheStats, type CloudDeploymentConfig, type CloudHostingConfig, type CloudMarketplaceEntry, CloudOperations, ConcurrentUpdateError, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, type ContractValidationError, type ContractValidationResult, type DataMaskingConfig, type DataMaskingRule, type EmailIntegrationConfig, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, type MetadataAuditEntry, type MetadataAuditResponse, MetadataClient, type MetadataClientConfig, type MetadataDeleteOptions, type MetadataDiagnostics, type MetadataDiagnosticsEntry, type MetadataDiagnosticsOptions, type MetadataDiagnosticsSummary, type MetadataDraftHeader, type MetadataError, type MetadataGetOptions, type MetadataHistoryOptions, type MetadataLayered, type MetadataListOptions, MetadataNotFoundError, type MetadataReference, type MetadataSaveOptions, type MetadataValidationIssue, ObjectStackAdapter, ObjectStackError, type ObjectStackUserStateAdapterOptions, type PluginAPIContract, type PluginContract, type PluginExport, SecurityManager, type SecurityPolicy, type SlackIntegrationConfig, type StudioCanvasConfig, type StudioColorPalette, type StudioPropertyEditor, type StudioShadowPreset, type StudioThemeBuilderConfig, type StudioTypographyPreset, type UserDataAdapter, ValidationError, type WebhookIntegrationConfig, calculateAutoLayout, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, generateContractManifest, getSharedDiscovery, is404Error, isConcurrentUpdateError, isErrorType, isObjectStackError, normaliseClientError, readTransactionalBatchCapability, snapToGrid, validatePluginContract };
2214
+ export { AnalyticsNotInstalledError, AnalyticsQueryRejectedError, type AuditEventType, type AuditLogConfig, type AuditLogEntry, AuthenticationError, type BatchProgressEvent, type BatchProgressListener, BulkOperationError, type CSPConfig, type CloudDeploymentConfig, type CloudHostingConfig, type CloudMarketplaceEntry, CloudOperations, ConcurrentUpdateError, ConnectionError, type ConnectionState, type ConnectionStateEvent, type ConnectionStateListener, type ContractValidationError, type ContractValidationResult, DataApiValidationError, type DataMaskingConfig, type DataMaskingRule, type EmailIntegrationConfig, FILTER_OPERATOR_ALIASES, type IntegrationConfig, IntegrationManager, type IntegrationProvider, type IntegrationTrigger, MalformedFilterError, type MetadataAuditEntry, type MetadataAuditResponse, type MetadataCacheStats, MetadataClient, type MetadataClientConfig, type MetadataClientSaveOptions, type MetadataDeleteOptions, type MetadataDiagnostics, type MetadataDiagnosticsEntry, type MetadataDiagnosticsOptions, type MetadataDiagnosticsSummary, type MetadataDraftHeader, type MetadataError, type MetadataGetOptions, type MetadataHistoryOptions, type MetadataLayered, type MetadataListOptions, MetadataNotFoundError, type MetadataReference, type MetadataValidationIssue, ObjectStackAdapter, ObjectStackError, type ObjectStackUserStateAdapterOptions, type PluginAPIContract, type PluginContract, type PluginExport, SecurityManager, type SecurityManagerPolicy, type SlackIntegrationConfig, type StudioCanvasConfig, type StudioColorPalette, type StudioPropertyEditor, type StudioShadowPreset, type StudioThemeBuilderConfig, type StudioTypographyPreset, type UserDataAdapter, type WebhookIntegrationConfig, type WriteWarningEvent, type WriteWarningListener, calculateAutoLayout, classifyAnalyticsFailure, clearSharedDiscoveryCache, createDefaultCanvasConfig, createErrorFromResponse, createObjectStackAdapter, createObjectStackUserStateAdapter, generateContractManifest, getSharedDiscovery, is404Error, isAnalyticsNotInstalledError, isConcurrentUpdateError, isErrorType, isMalformedFilterError, isObjectStackError, normaliseClientError, readTransactionalBatchCapability, serializeOrderBy, snapToGrid, validatePluginContract };