@holistics/anfra-sdk 0.6.1 → 0.7.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/api.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { Client } from 'openapi-fetch';
2
- import { D as DatasetDescriptor, B as Backend, f as BackendQueryRequest } from './types-UjrcOty4.js';
2
+ import { D as DatasetDescriptor, B as Backend, g as BackendQueryRequest } from './types-BlzJVcUl.js';
3
3
  import { D as DataAppError } from './errors-CKvqVaqU.js';
4
4
 
5
5
  /**
@@ -59,6 +59,28 @@ interface paths {
59
59
  patch?: never;
60
60
  trace?: never;
61
61
  };
62
+ "/core.query.export": {
63
+ parameters: {
64
+ query?: never;
65
+ header?: never;
66
+ path?: never;
67
+ cookie?: never;
68
+ };
69
+ get?: never;
70
+ put?: never;
71
+ /**
72
+ * Export a query's whole result as a file: a link to download it
73
+ * @description Export a query's whole result as a file: a link to download it.
74
+ *
75
+ * The query runs to completion before the answer, so a query that fails fails here. The CLI downloads the file, to stdout or --output; --link prints the answer instead.
76
+ */
77
+ post: operations["core.query.export"];
78
+ delete?: never;
79
+ options?: never;
80
+ head?: never;
81
+ patch?: never;
82
+ trace?: never;
83
+ };
62
84
  "/core.query.validate": {
63
85
  parameters: {
64
86
  query?: never;
@@ -179,6 +201,36 @@ interface components {
179
201
  message: string;
180
202
  severity: string;
181
203
  };
204
+ Column: {
205
+ /** @description a query-local expression, not a field the dataset defines */
206
+ adhoc: boolean;
207
+ /** @description an aggregated field's aggregation, such as sum or count distinct */
208
+ aggregation?: string;
209
+ /** @description the Arrow type canal-query read the values as, by which they are written; absent before the query runs */
210
+ arrow_type?: {
211
+ /** @description the type as arrow-go spells it, such as decimal(38, 2): for people, not to decide on */
212
+ label: string;
213
+ /** @description the type's name, as Schema.fbs's Type union names it, in snake_case: int, floating_point, decimal, utf8, bool, date, timestamp, list, struct, ... */
214
+ type: string;
215
+ } & {
216
+ [key: string]: unknown;
217
+ };
218
+ /** @description the database's own name of the column's type: for debugging, never to decide on, since it varies by database and driver; absent before the query runs */
219
+ db_type?: string;
220
+ /** @description the field it draws; for an adhoc column, its name */
221
+ fieldName: string;
222
+ isMeasure: boolean;
223
+ label: string;
224
+ /** @description the model of fieldName, as the dataset names it; absent for a dataset metric or an adhoc column */
225
+ modelId?: string;
226
+ /** @description the column's key, as in fields */
227
+ name: string;
228
+ /**
229
+ * @description what the column's values mean, in AML's types, plus bit: a true or false stored as a bit, written as true and false or as "1" and "0" by how the database produced it. Decide by it. Absent when no AQL types the column (a SQL query, or AQL that is not an explore): see arrow_type. Unknown when its AQL type has no meaning in AML (time, duration, ...)
230
+ * @enum {string}
231
+ */
232
+ semantic_type?: "bit" | "date" | "datetime" | "json" | "number" | "text" | "truefalse" | "unknown";
233
+ };
182
234
  CompileError: {
183
235
  /** Format: int64 */
184
236
  col?: number;
@@ -190,8 +242,8 @@ interface components {
190
242
  CompiledQuery: {
191
243
  /** @description the AQL compiled: the query with its Query Input applied; absent for a SQL query */
192
244
  aql?: string;
193
- /** @description what each column of an explore query's answer will be; absent for other queries */
194
- columns?: components["schemas"]["ExploreColumn"][];
245
+ /** @description what each column of an explore query's answer will be, and its semantic type; absent for other queries */
246
+ columns?: components["schemas"]["Column"][];
195
247
  sql: string;
196
248
  };
197
249
  ContextEntry: {
@@ -201,6 +253,18 @@ interface components {
201
253
  step: string;
202
254
  text: string;
203
255
  };
256
+ /** @description This server cannot make exports. */
257
+ ErrorBodyExportsUnavailable: {
258
+ /** @constant */
259
+ code: "exports_unavailable";
260
+ context?: components["schemas"]["ContextEntry"][];
261
+ message: string;
262
+ request_id: string;
263
+ /** @constant */
264
+ scope: "server";
265
+ /** @constant */
266
+ status: 501;
267
+ };
204
268
  /** @description Something went wrong on our side. */
205
269
  ErrorBodyInternalServerError: {
206
270
  /** @constant */
@@ -276,19 +340,60 @@ interface components {
276
340
  /** @constant */
277
341
  status: 422;
278
342
  };
279
- ExploreColumn: {
280
- /** @description a query-local expression, not a field the dataset defines */
281
- adhoc: boolean;
282
- /** @description an aggregated field's aggregation, such as sum or count distinct */
283
- aggregation?: string;
284
- /** @description the field it draws; for an adhoc column, its name */
285
- fieldName: string;
286
- isMeasure: boolean;
287
- label: string;
288
- /** @description the model of fieldName, as the dataset names it; absent for a dataset metric or an adhoc column */
289
- modelId?: string;
290
- /** @description the column's key, as in fields */
291
- name: string;
343
+ ExportFormatOptions: {
344
+ /** @description csv: start the file with a UTF-8 byte-order mark, which Excel needs to read non-ASCII text; set it for a file a person will open */
345
+ bom?: boolean;
346
+ /**
347
+ * @description csv: the header row: the columns' labels (the default; what readers see), their names (their keys in a query's answer, stable for programs), or none
348
+ * @enum {string}
349
+ */
350
+ header?: "labels" | "names" | "none";
351
+ };
352
+ ExportInput: {
353
+ /** @description the data source a SQL query runs against */
354
+ data_source?: string;
355
+ /** @description the dataset an AQL query runs against */
356
+ dataset?: string;
357
+ /** @description the file's name, as a download saves it, with the format's extension added when it has none; when unset or blank, the dataset's or data source's name */
358
+ filename?: string;
359
+ /**
360
+ * @description the file's format
361
+ * @default csv
362
+ * @enum {string}
363
+ */
364
+ format?: "csv";
365
+ /** @description the format's options */
366
+ format_options?: components["schemas"]["ExportFormatOptions"];
367
+ /** @description the Query Input: filters, conditions, sorts and date drills applied to the AQL before it compiles */
368
+ input?: components["schemas"]["QueryTransforms"];
369
+ /**
370
+ * @description the language the query is written in
371
+ * @default aql
372
+ * @enum {string}
373
+ */
374
+ lang?: "aql" | "sql";
375
+ /** @description the query */
376
+ query: string;
377
+ /** @description the IANA time zone relative dates and date truncation use, such as Asia/Ho_Chi_Minh */
378
+ timezone?: string;
379
+ };
380
+ ExportLink: {
381
+ /**
382
+ * Format: date-time
383
+ * @description when the link stops working
384
+ */
385
+ expires_at: string;
386
+ /** @description the file's name, as a download saves it */
387
+ filename: string;
388
+ /** @description the file's format */
389
+ format: string;
390
+ /**
391
+ * Format: int64
392
+ * @description the rows the file holds
393
+ */
394
+ row_count: number;
395
+ /** @description where to download the file: a plain GET, with no credentials, until expires_at */
396
+ url: string;
292
397
  };
293
398
  IngestInput: {
294
399
  /** @description optional context source key to ingest */
@@ -314,7 +419,7 @@ interface components {
314
419
  modifier?: string;
315
420
  /** @description the operator, such as is, contains, between, last */
316
421
  operator: string;
317
- /** @description the operator's values: strings, numbers or booleans; empty for an operator that takes none */
422
+ /** @description the operator's values, by the field's type: a number as a string in JSON's number syntax, such as "9007199254740993" (a JSON number is refused, since it may have lost digits before it was sent); true or false; a date or text as a string. Empty for an operator that takes none */
318
423
  values: unknown[];
319
424
  };
320
425
  QueryInput: {
@@ -335,12 +440,13 @@ interface components {
335
440
  /** @description the AQL that ran: the query with its Query Input applied; absent for a SQL query */
336
441
  aql?: string;
337
442
  /** @description one per field of result, in order */
338
- columns: components["schemas"]["ExploreColumn"][];
443
+ columns: components["schemas"]["Column"][];
339
444
  result: components["schemas"]["QueryRows"];
340
445
  sql: string;
341
446
  };
342
447
  QueryRows: {
343
448
  fields: string[];
449
+ /** @description one array per row, its values in fields' order, as its column's types say: every number a string, with every digit (a JSON number loses some in JavaScript), a float's NaN and infinities as "NaN", "Infinity" and "-Infinity", and so at any depth of a list, map or struct; null as null */
344
450
  records: unknown[][];
345
451
  };
346
452
  QueryRunInput: {
@@ -753,6 +859,96 @@ interface operations {
753
859
  };
754
860
  };
755
861
  };
862
+ "core.query.export": {
863
+ parameters: {
864
+ query?: never;
865
+ header?: never;
866
+ path?: never;
867
+ cookie?: never;
868
+ };
869
+ requestBody: {
870
+ content: {
871
+ "application/json": components["schemas"]["ExportInput"];
872
+ };
873
+ };
874
+ responses: {
875
+ /** @description OK */
876
+ 200: {
877
+ headers: {
878
+ [name: string]: unknown;
879
+ };
880
+ content: {
881
+ "application/json": components["schemas"]["ExportLink"];
882
+ };
883
+ };
884
+ /** @description invalid_request */
885
+ 400: {
886
+ headers: {
887
+ [name: string]: unknown;
888
+ };
889
+ content: {
890
+ "application/json": {
891
+ error: components["schemas"]["ErrorBodyInvalidRequest"];
892
+ };
893
+ };
894
+ };
895
+ /** @description validation_failed, query_invalid */
896
+ 422: {
897
+ headers: {
898
+ [name: string]: unknown;
899
+ };
900
+ content: {
901
+ "application/json": {
902
+ error: components["schemas"]["ErrorBodyValidationFailed"] | components["schemas"]["ErrorBodyQueryInvalid"];
903
+ };
904
+ };
905
+ };
906
+ /** @description internal_server_error */
907
+ 500: {
908
+ headers: {
909
+ [name: string]: unknown;
910
+ };
911
+ content: {
912
+ "application/json": {
913
+ error: components["schemas"]["ErrorBodyInternalServerError"];
914
+ };
915
+ };
916
+ };
917
+ /** @description exports_unavailable */
918
+ 501: {
919
+ headers: {
920
+ [name: string]: unknown;
921
+ };
922
+ content: {
923
+ "application/json": {
924
+ error: components["schemas"]["ErrorBodyExportsUnavailable"];
925
+ };
926
+ };
927
+ };
928
+ /** @description query_failed */
929
+ 502: {
930
+ headers: {
931
+ [name: string]: unknown;
932
+ };
933
+ content: {
934
+ "application/json": {
935
+ error: components["schemas"]["ErrorBodyQueryFailed"];
936
+ };
937
+ };
938
+ };
939
+ /** @description sidecar_unavailable */
940
+ 503: {
941
+ headers: {
942
+ [name: string]: unknown;
943
+ };
944
+ content: {
945
+ "application/json": {
946
+ error: components["schemas"]["ErrorBodySidecarUnavailable"];
947
+ };
948
+ };
949
+ };
950
+ };
951
+ };
756
952
  "core.query.validate": {
757
953
  parameters: {
758
954
  query?: never;
package/dist/api.js CHANGED
@@ -59,6 +59,17 @@ function toQueryInput(request) {
59
59
  ...request.timezone ? { timezone: request.timezone } : {}
60
60
  };
61
61
  }
62
+ function toColumns(columns) {
63
+ return columns.map((column) => {
64
+ const { semantic_type: semanticType, arrow_type: arrowType, db_type: dbType } = column;
65
+ if (semanticType === void 0 || arrowType === void 0 || dbType === void 0) {
66
+ throw new QueryError(
67
+ `The server answered column '${column.name}' without its types (semantic_type, arrow_type, db_type), which a Data App decides by: the server is older than this SDK.`
68
+ );
69
+ }
70
+ return { ...column, semantic_type: semanticType, arrow_type: arrowType, db_type: dbType };
71
+ });
72
+ }
62
73
  async function runQuery(client, body, signal) {
63
74
  let result;
64
75
  try {
@@ -78,7 +89,7 @@ function coreApiBackend(base, options = {}) {
78
89
  const data = await runQuery(client, toQueryInput(request), signal);
79
90
  const values = data.result.records;
80
91
  return {
81
- columns: data.columns,
92
+ columns: toColumns(data.columns),
82
93
  values,
83
94
  meta: {
84
95
  ...request.pageSize !== void 0 ? { page: request.page ?? 1, pageSize: request.pageSize } : {},
package/dist/app.d.ts CHANGED
@@ -1,8 +1,8 @@
1
- import { L as Listener, $ as Unsubscribe, D as DatasetDescriptor, E as EntityKind, i as Condition, v as FilterDeclaration, w as FilterValueType, B as Backend, j as ConditionValue, m as DateDrillDeclaration, n as DateGrain, u as FilterAggregation, k as ControlMappingJson, l as CrossFilterJson, W as SdkFeatures, X as Selection, Y as SelectionCondition, U as Row, J as LocateKind, N as QueryDeclaration, T as QueryState, R as QueryResult, S as QuerySort, a as AppDeclaration, r as ExecuteOptions, s as ExecuteSummary, b as AppJson, I as InspectedApp, a0 as User, V as SdkEnvironment, H as InspectedStructure } from './types-UjrcOty4.js';
2
- export { A as Aggregation, c as BackendCondition, d as BackendDateDrill, e as BackendFilter, f as BackendQueryRequest, g as BackendQueryResult, h as BackendSort, C as ColumnMeta, o as DateTransformation, p as EntityRef, q as EntityUsage, F as FILTER_AGGREGATIONS, t as FieldSuggestionsRequest, x as InspectedControl, y as InspectedError, z as InspectedQuery, G as InspectedSelection, K as LocateTarget, M as MappingJson, O as Operator, Q as QueryDebug, P as QueryInput, Z as StructureKind, _ as StructureNode } from './types-UjrcOty4.js';
1
+ import { L as Listener, a1 as Unsubscribe, D as DatasetDescriptor, E as EntityKind, j as Condition, w as FilterDeclaration, x as FilterValueType, B as Backend, k as ConditionValue, n as DateDrillDeclaration, o as DateGrain, v as FilterAggregation, l as ControlMappingJson, m as CrossFilterJson, X as SdkFeatures, Y as Selection, Z as SelectionCondition, V as Row, K as LocateKind, P as QueryDeclaration, U as QueryState, S as QueryResult, T as QuerySort, a as AppDeclaration, s as ExecuteOptions, t as ExecuteSummary, b as AppJson, I as InspectedApp, a2 as User, W as SdkEnvironment, J as InspectedStructure } from './types-BlzJVcUl.js';
2
+ export { A as Aggregation, c as ArrowType, d as BackendCondition, e as BackendDateDrill, f as BackendFilter, g as BackendQueryRequest, h as BackendQueryResult, i as BackendSort, C as ColumnMeta, p as DateTransformation, q as EntityRef, r as EntityUsage, F as FILTER_AGGREGATIONS, u as FieldSuggestionsRequest, y as InspectedControl, z as InspectedError, G as InspectedQuery, H as InspectedSelection, M as LocateTarget, N as MappingJson, O as Operator, Q as QueryDebug, R as QueryInput, _ as SemanticType, $ as StructureKind, a0 as StructureNode } from './types-BlzJVcUl.js';
3
3
  import { D as DataAppError } from './errors-CKvqVaqU.js';
4
4
  export { P as PermissionError, Q as QueryError, T as TransportError, V as ValidationError } from './errors-CKvqVaqU.js';
5
- export { B as BridgeError, a as BridgeMethod, F as FrameMessage, H as HostMessage, P as PROVISION_ELEMENT_ID, b as Provision } from './bridge-Dn2jipCu.js';
5
+ export { B as BridgeError, a as BridgeMethod, F as FrameMessage, H as HostMessage, P as PROVISION_ELEMENT_ID, b as Provision } from './bridge-BKx7u5mX.js';
6
6
 
7
7
  interface NotificationTarget {
8
8
  notifyChange(): void;
@@ -93,6 +93,13 @@ declare class Filter extends Control {
93
93
  private readonly backend?;
94
94
  private loadedOptions?;
95
95
  constructor(name: string, app: AppContext & Observable, declaration: FilterDeclaration, dataset?: DatasetIndex, backend?: Backend);
96
+ setCondition(condition: Condition): void;
97
+ /**
98
+ * A number filter's values are strings, as query results give them, which keep every digit. A JS
99
+ * number may already have lost some (`Number("9007199254740993")` is ...992), and nothing after
100
+ * can tell, so the core API refuses one; this says so where the app hands it over.
101
+ */
102
+ private assertValues;
96
103
  /** True for a field-backed filter, false for a manual one. The two are separate axes from `type`. */
97
104
  get isFieldBacked(): boolean;
98
105
  /** A manual filter's static list, or whatever the last `loadOptions()` returned. */
package/dist/app.js CHANGED
@@ -347,6 +347,26 @@ var Filter = class extends Control {
347
347
  }
348
348
  this.type = declaration.type;
349
349
  }
350
+ this.assertValues(this.condition.values ?? []);
351
+ this.assertValues(declaration.options ?? []);
352
+ }
353
+ setCondition(condition) {
354
+ this.assertValues(condition.values ?? []);
355
+ super.setCondition(condition);
356
+ }
357
+ /**
358
+ * A number filter's values are strings, as query results give them, which keep every digit. A JS
359
+ * number may already have lost some (`Number("9007199254740993")` is ...992), and nothing after
360
+ * can tell, so the core API refuses one; this says so where the app hands it over.
361
+ */
362
+ assertValues(values) {
363
+ if (this.type !== "number") return;
364
+ const number = values.find((value) => typeof value === "number");
365
+ if (number === void 0) return;
366
+ throw new ValidationError(
367
+ `Filter '${this.name}' filters numbers: give its values as strings, as query results give them (${JSON.stringify(String(number))}, not ${String(number)}), so none has lost a digit.`,
368
+ this.name
369
+ );
350
370
  }
351
371
  /** True for a field-backed filter, false for a manual one. The two are separate axes from `type`. */
352
372
  get isFieldBacked() {
@@ -639,9 +659,10 @@ async function runQuery(query, mappings, selection, context, signal) {
639
659
  // src/app/aql.ts
640
660
  var LINE_SEPARATOR = String.fromCharCode(8232);
641
661
  var PARAGRAPH_SEPARATOR = String.fromCharCode(8233);
642
- function literal(value) {
662
+ function literal(value, semanticType) {
643
663
  if (value === null) return "null";
644
664
  if (typeof value === "boolean") return value ? "true" : "false";
665
+ if (semanticType === "number" && typeof value === "string") return numberLiteral(value);
645
666
  if (typeof value === "number") {
646
667
  if (!Number.isFinite(value)) {
647
668
  throw new RangeError(`Cannot express ${value} in AQL.`);
@@ -650,8 +671,26 @@ function literal(value) {
650
671
  }
651
672
  return JSON.stringify(value).split(LINE_SEPARATOR).join("\\u2028").split(PARAGRAPH_SEPARATOR).join("\\u2029");
652
673
  }
653
- function equals(field, value) {
654
- return value === null ? `is(${field}, null)` : `${field} is ${literal(value)}`;
674
+ function equals(field, value, semanticType) {
675
+ return value === null ? `is(${field}, null)` : `${field} is ${literal(value, semanticType)}`;
676
+ }
677
+ var NON_FINITE = /* @__PURE__ */ new Set(["NaN", "Infinity", "-Infinity"]);
678
+ var JSON_NUMBER = /^(-?)(0|[1-9]\d*)(?:\.(\d+))?(?:[eE]([+-]?\d+))?$/;
679
+ function numberLiteral(value) {
680
+ const match = JSON_NUMBER.exec(value);
681
+ if (!match) {
682
+ if (NON_FINITE.has(value)) throw new RangeError(`Cannot express ${value} in AQL.`);
683
+ throw new TypeError(`${JSON.stringify(value)} is not a number: a number is a string in JSON's number syntax.`);
684
+ }
685
+ const [, sign, whole, fraction = "", exponent] = match;
686
+ if (exponent === void 0) return value;
687
+ const digits = whole + fraction;
688
+ const point = whole.length + Number(exponent);
689
+ let out;
690
+ if (point <= 0) out = `0.${"0".repeat(-point)}${digits}`;
691
+ else if (point >= digits.length) out = digits + "0".repeat(point - digits.length);
692
+ else out = `${digits.slice(0, point)}.${digits.slice(point)}`;
693
+ return sign + out.replace(/^0+(?=\d)/, "");
655
694
  }
656
695
  function dateRange(field, start, end) {
657
696
  return `${field} matches @(${start} until ${end})`;
@@ -780,7 +819,8 @@ function fieldReferenceOf(column) {
780
819
  function selectionExpression(columnsByAlias, aliases, rows, transformations) {
781
820
  try {
782
821
  const perRow = rows.map((row) => and(aliases.map((alias) => {
783
- const field = fieldReferenceOf(columnsByAlias.get(alias));
822
+ const column = columnsByAlias.get(alias);
823
+ const field = fieldReferenceOf(column);
784
824
  const raw = row[alias] ?? null;
785
825
  const grain = grainOf(transformations[field]);
786
826
  if (grain && typeof raw === "string") {
@@ -791,7 +831,7 @@ function selectionExpression(columnsByAlias, aliases, rows, transformations) {
791
831
  if (raw !== null && typeof raw !== "string" && typeof raw !== "number" && typeof raw !== "boolean") {
792
832
  throw new TypeError("no AQL spelling");
793
833
  }
794
- return equals(field, raw);
834
+ return equals(field, raw, column.semantic_type);
795
835
  })));
796
836
  return or(perRow);
797
837
  } catch {
@@ -1621,7 +1661,7 @@ var App = class extends Observable {
1621
1661
  };
1622
1662
 
1623
1663
  // package.json
1624
- var version = "0.6.1";
1664
+ var version = "0.7.0";
1625
1665
 
1626
1666
  // src/app/sdk.ts
1627
1667
  var VERSION = version;
@@ -1,4 +1,4 @@
1
- import { f as BackendQueryRequest, t as FieldSuggestionsRequest, I as InspectedApp, H as InspectedStructure, K as LocateTarget, D as DatasetDescriptor, a0 as User } from './types-UjrcOty4.js';
1
+ import { g as BackendQueryRequest, u as FieldSuggestionsRequest, I as InspectedApp, J as InspectedStructure, M as LocateTarget, D as DatasetDescriptor, a2 as User } from './types-BlzJVcUl.js';
2
2
 
3
3
  /**
4
4
  * The bridge: the postMessage protocol between a Data App's frame (`app`'s bootstrap) and the
package/dist/common.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  export { D as DataAppError, P as PermissionError, Q as QueryError, T as TransportError, V as ValidationError } from './errors-CKvqVaqU.js';
2
- export { A as Aggregation, a as AppDeclaration, b as AppJson, B as Backend, c as BackendCondition, d as BackendDateDrill, e as BackendFilter, f as BackendQueryRequest, g as BackendQueryResult, h as BackendSort, C as ColumnMeta, i as Condition, j as ConditionValue, k as ControlMappingJson, l as CrossFilterJson, D as DatasetDescriptor, m as DateDrillDeclaration, n as DateGrain, o as DateTransformation, E as EntityKind, p as EntityRef, q as EntityUsage, r as ExecuteOptions, s as ExecuteSummary, F as FILTER_AGGREGATIONS, t as FieldSuggestionsRequest, u as FilterAggregation, v as FilterDeclaration, w as FilterValueType, I as InspectedApp, x as InspectedControl, y as InspectedError, z as InspectedQuery, G as InspectedSelection, H as InspectedStructure, L as Listener, J as LocateKind, K as LocateTarget, M as MappingJson, O as Operator, Q as QueryDebug, N as QueryDeclaration, P as QueryInput, R as QueryResult, S as QuerySort, T as QueryState, U as Row, V as SdkEnvironment, W as SdkFeatures, X as Selection, Y as SelectionCondition, Z as StructureKind, _ as StructureNode, $ as Unsubscribe, a0 as User } from './types-UjrcOty4.js';
3
- export { B as BridgeError, a as BridgeMethod, F as FrameMessage, H as HostMessage, P as PROVISION_ELEMENT_ID, b as Provision } from './bridge-Dn2jipCu.js';
2
+ export { A as Aggregation, a as AppDeclaration, b as AppJson, c as ArrowType, B as Backend, d as BackendCondition, e as BackendDateDrill, f as BackendFilter, g as BackendQueryRequest, h as BackendQueryResult, i as BackendSort, C as ColumnMeta, j as Condition, k as ConditionValue, l as ControlMappingJson, m as CrossFilterJson, D as DatasetDescriptor, n as DateDrillDeclaration, o as DateGrain, p as DateTransformation, E as EntityKind, q as EntityRef, r as EntityUsage, s as ExecuteOptions, t as ExecuteSummary, F as FILTER_AGGREGATIONS, u as FieldSuggestionsRequest, v as FilterAggregation, w as FilterDeclaration, x as FilterValueType, I as InspectedApp, y as InspectedControl, z as InspectedError, G as InspectedQuery, H as InspectedSelection, J as InspectedStructure, L as Listener, K as LocateKind, M as LocateTarget, N as MappingJson, O as Operator, Q as QueryDebug, P as QueryDeclaration, R as QueryInput, S as QueryResult, T as QuerySort, U as QueryState, V as Row, W as SdkEnvironment, X as SdkFeatures, Y as Selection, Z as SelectionCondition, _ as SemanticType, $ as StructureKind, a0 as StructureNode, a1 as Unsubscribe, a2 as User } from './types-BlzJVcUl.js';
3
+ export { B as BridgeError, a as BridgeMethod, F as FrameMessage, H as HostMessage, P as PROVISION_ELEMENT_ID, b as Provision } from './bridge-BKx7u5mX.js';
package/dist/host.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { I as InspectedApp, H as InspectedStructure, K as LocateTarget, B as Backend, D as DatasetDescriptor, a0 as User } from './types-UjrcOty4.js';
2
- import { b as Provision } from './bridge-Dn2jipCu.js';
1
+ import { I as InspectedApp, J as InspectedStructure, M as LocateTarget, B as Backend, D as DatasetDescriptor, a2 as User } from './types-BlzJVcUl.js';
2
+ import { b as Provision } from './bridge-BKx7u5mX.js';
3
3
 
4
4
  interface BridgeHandle {
5
5
  /** Ask the frame to post (or stop posting) inspection snapshots; survives the frame reloading. */
package/dist/host.js CHANGED
@@ -71,7 +71,7 @@ function serveBridge(frame, backend, callbacks = {}) {
71
71
  }
72
72
 
73
73
  // frame-script:anfra-sdk:frame-script
74
- var anfra_sdk_frame_script_default = '"use strict";(()=>{var ze=Object.defineProperty;var Pe=(t,n,e)=>n in t?ze(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var l=(t,n,e)=>Pe(t,typeof n!="symbol"?n+"":n,e);var $=class extends Error{constructor(e,r){super(e);l(this,"entity");this.name=new.target.name,this.entity=r,Object.setPrototypeOf(this,new.target.prototype)}},u=class extends ${},F=class extends ${constructor(e,r,i){super(e,r);l(this,"cause");this.cause=i}},P=class extends ${},V=class extends ${constructor(e,r,i){super(e,i);l(this,"status");this.status=r}};var ae="anfra-provision";var O=class{constructor(n){l(this,"listeners",new Set);l(this,"parent");this.parent=n}subscribe(n){return this.listeners.add(n),()=>{this.listeners.delete(n)}}notifyChange(){this.listeners.forEach(n=>n()),this.parent?.notifyChange()}notify(){this.notifyChange()}};var N=class extends O{constructor(e,r,i){super(r);l(this,"kind","query");l(this,"name");l(this,"declaration");l(this,"dataset");l(this,"app");l(this,"_state","idle");l(this,"_result");l(this,"_error");l(this,"_page",1);l(this,"_sort",[]);l(this,"lastRunSignature");l(this,"lastPageRows",0);l(this,"controller");if(!i.aql?.trim())throw new u(`Query \'${e}\' declares no AQL.`,e);let{pageSize:o}=i;if(o!==void 0&&!(Number.isInteger(o)&&o>=1))throw new u(`Query \'${e}\' declares pageSize ${o}; it must be a whole number of at least 1.`,e);this.name=e,this.app=r,this.declaration=i,this.dataset=r.datasetIndex(i.dataset,e)}locate(){return this.app.locate("query",this.name)}get state(){return this._state}get result(){return this._result}get error(){return this._error}get pageSize(){return this.declaration.pageSize}get page(){return this._page}get sort(){return this._sort}setSort(e){this._sort=[...e],this.notify()}get hasMore(){return!this._result||this.pageSize===void 0?!1:this.lastPageRows>=this.pageSize}signature(){let e=this.app.mappingsTo(this.name).map(r=>({field:r.field,aggregation:r.aggregation,condition:r.from.condition})).sort((r,i)=>r.field.localeCompare(i.field)||JSON.stringify(r).localeCompare(JSON.stringify(i)));return JSON.stringify({declaration:this.declaration,conditions:e,selection:this.app.selectionConditionsFor(this.name,"pending"),selectionExpression:this.app.selectionExpressionFor(this.name,"pending"),sort:this._sort,timezone:this.app.timezone,page:this._page})}get isDirty(){return this.lastRunSignature!==void 0&&this.signature()!==this.lastRunSignature}shouldRun(e=!1){return e||this.lastRunSignature===void 0||this.isDirty}abort(){this.controller?.abort()}async fetchMore(){if(this.pageSize===void 0)throw new u(`Query \'${this.name}\' declares no pageSize, so it has no more pages.`,this.name);this.hasMore&&(this._page+=1,await this.app.fetchPage(this))}select(e,r={}){this.app.select(this,Array.isArray(e)?e:[e],r.fields)}get selectedRows(){let{selection:e}=this.app;if(e?.source!==this.name)return[];let r=this._result?.rows;if(!r)return e.rows;let i=d=>JSON.stringify(e.fields.map(s=>d[s]??null)),o=new Set(e.rows.map(i));return r.filter(d=>o.has(i(d)))}markExecuting(){this._state="executing",this._error=void 0,this.notify()}markSuccess(e,r,i){if(this._state="success",this._error=void 0,this.lastRunSignature=r,this.lastPageRows=e.rows.length,i&&this._result){let o=[...this._result.rows,...e.rows];this._result={...e,rows:o,meta:{...e.meta,numRows:o.length}}}else this._result=e;this.notify()}markError(e){this._state="error",this._error=e,this.notify()}rewindPage(){this._page>1&&(this._page-=1)}resetPage(){this._page=1,this.lastPageRows=0}toJSON(){return this.declaration}};var le=["year","quarter","month","week","day","hour","minute"];function Ve(t,n){if(!le.includes(n))throw new u(`Invalid grain \'${n}\'. Expected one of: ${le.join(", ")}.`,t)}function de(t,n){return n?(Ve(t,n),{operator:"transform_date_drill",values:[`datetrunc ${n}`]}):{operator:"none"}}function Je(t){let n=t.toLowerCase();return n.includes("date")||n.includes("time")?"date":n.includes("int")||n.includes("number")||n.includes("float")||n.includes("decimal")?"number":n.includes("bool")?"boolean":"string"}function je(t,n){return JSON.stringify(t??null)===JSON.stringify(n??null)}var I=class extends O{constructor(e,r,i){super(r);l(this,"name");l(this,"app");l(this,"pending");l(this,"applied");l(this,"initial");this.name=e,this.app=r,this.initial=i,this.pending=i}locate(){return this.app.locate("control",this.name)}get condition(){return this.pending}get appliedCondition(){return this.applied}get isDirty(){return this.applied!==void 0&&!je(this.pending,this.applied)}setCondition(e){this.pending=e,this.notify()}reset(){this.setCondition(this.initial)}commit(){this.applied=this.pending}},J=class extends I{constructor(e,r,i,o,d){super(e,r,i.default??{operator:"is",values:[]});l(this,"kind","filter");l(this,"declaration");l(this,"type");l(this,"resolved");l(this,"datasetName");l(this,"backend");l(this,"loadedOptions");if(this.declaration=i,this.backend=d,i.field){if(!o)throw new u(`Filter \'${e}\' names a field but no dataset. Pass \\`dataset\\` alongside \\`field\\`.`,e);this.resolved=o.resolve(i.field,e),this.datasetName=o.descriptor.name,this.type=i.type??Je(this.resolved.type)}else{if(!i.type)throw new u(`Filter \'${e}\' is a manual filter, so it needs a \\`type\\`.`,e);this.type=i.type}}get isFieldBacked(){return this.resolved!==void 0}get options(){return this.loadedOptions??this.declaration.options??[]}async loadOptions(e,r=new AbortController().signal){let i=this.resolved?.modelName;if(!this.resolved||!i||!this.backend||!this.datasetName)return this.options;let o=await this.backend.fieldSuggestions({dataset:this.datasetName,model:i,field:this.resolved.fieldName,q:e??""},r);return this.loadedOptions=[...o??[]],this.notify(),this.loadedOptions}toJSON(){return this.declaration}},j=class extends I{constructor(e,r,i={}){super(e,r,de(e,i.default));l(this,"kind","dateDrill");l(this,"declaration");this.declaration=i}setGrain(e){this.setCondition(de(this.name,e))}toJSON(){return this.declaration}};var G=class{constructor(n,e,r,i){l(this,"kind","control");l(this,"from");l(this,"to");l(this,"field");l(this,"aggregation");this.from=n,this.to=e,this.field=r,this.aggregation=i}get id(){return`${this.from.name}.${this.to.name}.${this.field}`}toJSON(){return{kind:this.kind,id:this.id,from:{kind:this.from.kind,name:this.from.name},to:{kind:"query",name:this.to.name},field:this.field,...this.aggregation?{aggregation:this.aggregation}:{}}}},q=class{constructor(n,e){l(this,"kind","crossFilter");l(this,"from");l(this,"to");this.from=n,this.to=e}get id(){return`${this.from.name}.${this.to.name}`}toJSON(){return{kind:this.kind,id:this.id,from:{kind:"query",name:this.from.name},to:{kind:"query",name:this.to.name}}}};var Z=["sum","avg","max","min","count","count distinct","median","stdev","stdevp","var","varp"];function Ge(t,n){let e=t.length+1,r=n.length+1,i=Array.from({length:r},(o,d)=>d);for(let o=1;o<e;o++){let d=[o];for(let s=1;s<r;s++)d[s]=Math.min(i[s]+1,d[s-1]+1,i[s-1]+(t[o-1]===n[s-1]?0:1));i=d}return i[r-1]}function qe(t,n){let e,r=1/0;n.forEach(o=>{let d=Ge(t.toLowerCase(),o.toLowerCase());d<r&&(r=d,e=o)});let i=Math.max(2,Math.floor(t.length/3));return r<=i?e:void 0}function M(t,n,e){let r=qe(n,e);return r?`${t} Did you mean \'${r}\'?`:t}var U=class{constructor(n){l(this,"descriptor");l(this,"fields",new Map);l(this,"references");this.descriptor=n,n.data_models.forEach(e=>{e.fields.forEach(r=>{let i=String(e.name);this.fields.set(`${i}.${r.name}`,{modelName:i,fieldName:r.name,isMetric:!1,type:r.type,label:r.label??r.name})})}),n.metrics.forEach(e=>{this.fields.set(e.name,{fieldName:e.name,isMetric:!0,type:e.type,label:e.label??e.name})}),this.references=[...this.fields.keys()]}resolve(n,e){let r=this.fields.get(n);if(r)return r;throw new u(M(`Unknown field \'${n}\' on dataset \'${this.descriptor.name}\'.`,n,this.references),e)}};function ce(t,n,e){let r=t[n];if(r)return r;throw new u(M(`Unknown dataset \'${n}\'.`,n,Object.keys(t)),e)}function ue(t,n){if(!Z.includes(t))throw new u(M(`Invalid filter aggregation \'${t}\'.`,t,[...Z]),n)}function pe(t,n,e){if(!n)throw new u(`A ${e} needs a name.`);for(let r of t)if(r===n)throw new u(`\'${n}\' is already declared in this app.`,n)}function Ue(t,n){if(!(!n||n.operator==="none"||n.operator==="transform_date_drill"||!["is_null","not_null","is_true","is_false","transform_pop_none"].includes(n.operator)&&!n.values?.length))return{field:t.field,operator:n.operator,values:n.values??[],...n.modifier?{modifier:n.modifier}:{},...t.aggregation?{aggregation:t.aggregation}:{}}}function Ke(t,n){if(n?.operator!=="transform_date_drill")return;let e=n.values?.[0];if(typeof e!="string")return;let r=e.replace(/^datetrunc\\s+/,"");return{field:t.field,grain:r}}function He(t,n){let e=n.columns??[],r=(n.values??[]).map(o=>{let d={};return e.forEach((s,a)=>{d[s.name]=o[a]}),d}),{pageSize:i}=t;return{columns:e,rows:r,meta:{...i!==void 0?{page:n.meta?.page??t.page,pageSize:n.meta?.pageSize??i}:{},numRows:n.meta?.numRows??r.length},...n.debug?{debug:n.debug}:{}}}function We(t,n,e,r){let i=n.map(a=>Ue(a,a.from.appliedCondition)).filter(a=>a!==void 0),o=n.map(a=>Ke(a,a.from.appliedCondition)).filter(a=>a!==void 0),d=e.conditions.map(a=>({field:a.field,operator:a.operator,values:a.values})),s=e.expression?[{expr:e.expression}]:[];return{dataset:t.dataset.descriptor.name,aql:t.declaration.aql,input:{filters:[...i,...d],conditions:s,sorts:t.sort.map(a=>({field:a.field,direction:a.direction??"asc"})),dateDrills:o},...t.pageSize!==void 0?{page:t.page,pageSize:t.pageSize}:{},...r.timezone?{timezone:r.timezone}:{},...r.bustCache?{bustCache:!0}:{}}}async function ee(t,n,e,r,i){let o=We(t,n,e,r),d=await r.backend.submitQuery(o,i);return He(t,d)}function Ye(t){if(t===null)return"null";if(typeof t=="boolean")return t?"true":"false";if(typeof t=="number"){if(!Number.isFinite(t))throw new RangeError(`Cannot express ${t} in AQL.`);return Math.abs(t)>=1e21?BigInt(t).toString():String(t)}return JSON.stringify(t).split("\\u2028").join("\\\\u2028").split("\\u2029").join("\\\\u2029")}function fe(t,n){return n===null?`is(${t}, null)`:`${t} is ${Ye(n)}`}function me(t,n,e){return`${t} matches @(${n} until ${e})`}function he(t,n){if(n.length===0)throw new RangeError(`\\`${t}\\` needs at least one operand.`);return n.length===1?n[0]:`${t}(${n.join(", ")})`}var ge=t=>he("and",t),ye=t=>he("or",t);var Xe=/^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2}))?)?/,Ze={week:7*864e5,day:864e5,hour:36e5,minute:6e4};function be(t){if(typeof t!="string")return;let n=Xe.exec(t);if(n)return{year:Number(n[1]),month:Number(n[2])-1,day:Number(n[3]),hour:Number(n[4]??0),minute:Number(n[5]??0),second:Number(n[6]??0),hasTime:n[4]!==void 0}}function L(t,n){let e=new Date(t),r=o=>String(o).padStart(2,"0"),i=`${e.getUTCFullYear()}-${r(e.getUTCMonth()+1)}-${r(e.getUTCDate())}`;return n?`${i} ${r(e.getUTCHours())}:${r(e.getUTCMinutes())}:${r(e.getUTCSeconds())}`:i}function we(t){return Date.UTC(t.year,t.month,t.day,t.hour,t.minute,t.second)}function te(t){let n=be(t);if(n)return L(we(n),n.hasTime)}function ne(t,n){let e=be(t);if(!e)return;let{year:r,month:i,day:o,hour:d,minute:s,second:a,hasTime:p}=e;return L(n==="year"?Date.UTC(r+1,i,o,d,s,a):n==="quarter"?Date.UTC(r,i+3,o,d,s,a):n==="month"?Date.UTC(r,i+1,o,d,s,a):we(e)+Ze[n],p)}function ke(t){if(t?.startsWith("datetrunc "))return t.slice(10)}function et(t){let n=new Set;return t.filter(e=>{let r=`${typeof e}:${String(e)}`;return n.has(r)?!1:(n.add(r),!0)})}function tt(t,n){let e=t.map(te);if(e.some(i=>i===void 0))return!1;let r=e.slice().sort();for(let i=1;i<r.length;i+=1)if(ne(r[i-1],n)!==r[i])return!1;return!0}function nt(t,n,e){let r=n.map(te);if(r.some(a=>a===void 0))return;let i=r.slice().sort(),o=i[0],d=ne(i[i.length-1],e);if(!d)return;let s=tt(n,e);return{condition:{field:t,operator:"matches",values:[`${o} until ${d}`]},admits:s?n.length:Number.POSITIVE_INFINITY,lossy:!s}}function rt(t,n,e){let r=e.filter(s=>s!=null),i=et(r);if(!i.length)return{condition:{field:t,operator:"is_null",values:[]},admits:1,lossy:!1};let o=r.length!==e.length,d=ke(n);if(d){let s=nt(t,i,d);if(s)return{...s,lossy:s.lossy||o}}return{condition:{field:t,operator:"is",values:i},admits:i.length,lossy:o}}function ve(t){return t.modelId?`${t.modelId}.${t.fieldName}`:t.fieldName}function it(t,n,e,r){try{let i=e.map(o=>ge(n.map(d=>{let s=ve(t.get(d)),a=o[d]??null,p=ke(r[s]);if(p&&typeof a=="string"){let b=te(a),S=ne(a,p);if(b&&S)return me(s,b,S)}if(a!==null&&typeof a!="string"&&typeof a!="number"&&typeof a!="boolean")throw new TypeError("no AQL spelling");return fe(s,a)})));return ye(i)}catch{return}}function Se(t,n,e,r={}){let i=t.result?.columns??[],o=new Map(i.map(h=>[h.name,h]));e?.forEach(h=>{let y=o.get(h);if(y){if(y.adhoc)throw new u(`Query \'${t.name}\' cannot cross-filter on \'${h}\': it is a query-local AQL expression, which only this query\'s result carries. The target query would receive a condition naming a field it has never heard of.`,t.name);if(y.isMeasure)throw new u(`Query \'${t.name}\' cannot cross-filter on \'${h}\': it is a measure, and a selection conditions dimensions only.`,t.name);return}throw new u(M(`Query \'${t.name}\' has no column \'${h}\' in its last result.`,h,[...o.keys()]),t.name)});let d=i.filter(h=>!h.isMeasure),s=d.filter(h=>!h.adhoc).map(h=>h.name),a=e?[...e]:s;if(!n.length||!a.length)return;let p=[],b=1,S=!e&&s.length!==d.length,f=!1;a.forEach(h=>{let y=o.get(h),_=ve(y),T=rt(_,r[_],n.map(w=>w[h]));p.push(T.condition),b*=T.admits,f=f||T.lossy});let v=new Set(n.map(h=>JSON.stringify(a.map(y=>h[y]??null))));b>v.size&&(f=!0);let x=p.sort((h,y)=>h.field.localeCompare(y.field)),E={source:t.name,rows:[...n],fields:a};if(f){let h=it(o,a,n,r);if(h)return{...E,conditions:[],expression:h,lossy:S}}return{...E,conditions:x,lossy:S||f}}var C={container:"data-anfra-container",block:"data-anfra-block",query:"data-anfra-query",control:"data-anfra-control",label:"data-anfra-label"},K=`[${C.container}],[${C.block}],[${C.query}],[${C.control}]`;function xe(t){let n=/^(?:(\\d+)\\/)?([^/\\s]+)$/.exec(t);if(n)return{app:n[1]===void 0?0:Number(n[1]),name:n[2]}}var Ee=t=>(t??"").split(/\\s+/).filter(Boolean);function Ce(t,n,e,r){let i=n==="query"?C.query:C.control;return[...t.querySelectorAll(`[${i}]`)].filter(o=>Ee(o.getAttribute(i)).some(d=>{let s=xe(d);return s?.app===e&&s.name===r}))}var ot=(t,n,e)=>{let r=t[e.app];return r?Object.prototype.hasOwnProperty.call(n==="query"?r.queries:r.controls,e.name):!1};function re(t,n){var S;let e=[],r=new Map,i=new Map,o=new Map,d=new Map,s={container:new Set,block:new Set},a={},p=(f,v,x)=>{let E=f?o.get(f).children:e,h=f?`${o.get(f).key}.`:"",y={...v,key:`${h}${E.length}`,children:[]};return E.push(y),r.set(y.key,x),i.set(x,[...i.get(x)??[],y.key]),y};for(let f of t.querySelectorAll(K)){let v=f.parentElement?.closest(K)??null,x=f.getAttribute(C.container),E=f.getAttribute(C.block),h=f.getAttribute(C.label)??void 0,y=v&&(d.get(v)==="block"?v:v.closest(`[${C.block}]`)),_;if(x!==null||E!==null){let w=x!==null?"container":"block",D=(x??E??"").trim(),k=[];x!==null&&E!==null&&k.push("This element is marked as a container and as a block. It counts as a container."),D?s[w].has(D)?k.push(`Another ${w} already has the id "${D}".`):s[w].add(D):k.push(`This ${w} has no id.`),y&&k.push(`A block can\'t hold a ${w}. This one is inside the block "${y.getAttribute(C.block)}".`),_=p(v,{kind:w,id:D,...h!==void 0?{label:h}:{},problems:k},f),o.set(f,_),d.set(f,w)}let T=_?f:v;for(let w of["query","control"])for(let D of Ee(f.getAttribute(w==="query"?C.query:C.control))){let k=xe(D),R=[];if(k?ot(n,w,k)||R.push(n[k.app]?`No ${w} is called "${k.name}" in app ${k.app}.`:`The page has no app ${k.app}.`):R.push(`Can\'t read "${D}". Write the ${w}\'s name, or app/name when the page has several apps.`),p(T,{kind:w,name:k?.name??D,app:k?.app??0,problems:R},f),k&&!R.length){let Y=`${k.app}/${k.name}`,c=a[S=`${w}:${Y}`]??(a[S]={elements:new Set,blocks:new Set});c.elements.add(f);let m=_?.kind==="block"?_.id:y?.getAttribute(C.block);m&&c.blocks.add(m)}}}let b=Object.fromEntries(Object.entries(a).map(([f,v])=>[f,{markers:v.elements.size,blocks:[...v.blocks]}]));return{nodes:e,usage:b,elements:r,keysOf:i}}var st=(t,n,e)=>`${t}:${n}/${e}`;function at(t){let n=t.getBoundingClientRect();return`${Math.round(n.width)} \\xD7 ${Math.round(n.height)}`}function De(t,n,e){let r=at(n);if(t.kind==="container"||t.kind==="block")return{kind:t.kind,name:t.id??"",...t.label?{label:t.label}:{},size:r};let i=e.usage[st(t.kind,t.app??0,t.name??"")];return{kind:t.kind,name:t.name??"",...i&&i.markers>1?{note:`1 of ${i.markers}`}:{},size:r}}var Ae="anfra-overlay";var lt="position:fixed;box-sizing:border-box;background:rgba(37,99,235,0.22);outline:1px solid #2563eb;outline-offset:-1px;",dt="position:fixed;max-width:60vw;display:flex;gap:6px;align-items:baseline;padding:3px 8px;border-radius:4px;background:#0f172a;color:#f8fafc;font:12px/1.4 system-ui,sans-serif;white-space:nowrap;box-shadow:0 2px 8px rgba(0,0,0,0.35);",ct={kind:"color:#94a3b8;",name:"color:#93c5fd;font-weight:700;",label:"color:#f8fafc;",note:"color:#94a3b8;",size:"color:#94a3b8;margin-left:10px;"},ie=class{constructor(n){l(this,"doc",n);l(this,"root");l(this,"shown",[]);l(this,"text",{kind:"",name:""});l(this,"timer");l(this,"reposition",()=>this.draw());let e=n.getElementById(Ae);e?this.root=e:(this.root=n.createElement("div"),this.root.id=Ae,this.root.setAttribute("aria-hidden","true"),this.root.setAttribute("style","position:fixed;inset:0;z-index:2147483647;pointer-events:none;display:none;"),(n.body??n.documentElement).appendChild(this.root))}show(n,e){if(this.clearTimer(),this.shown=[...n],this.text=e,!this.shown.length){this.hide();return}this.root.style.display="block",this.draw(),this.doc.addEventListener("scroll",this.reposition,{capture:!0,passive:!0}),this.doc.defaultView?.addEventListener("resize",this.reposition)}flash(n,e,r=1500){this.show(n,e),this.timer=setTimeout(()=>this.hide(),r)}hide(){this.clearTimer(),this.shown=[],this.root.style.display="none",this.root.replaceChildren(),this.doc.removeEventListener("scroll",this.reposition,{capture:!0}),this.doc.defaultView?.removeEventListener("resize",this.reposition)}get visible(){return this.shown.length>0}clearTimer(){this.timer!==void 0&&clearTimeout(this.timer),this.timer=void 0}draw(){let n=this.shown.map(s=>{let a=s.getBoundingClientRect(),p=this.doc.createElement("div");return p.setAttribute("style",`${lt}left:${a.left}px;top:${a.top}px;width:${a.width}px;height:${a.height}px;`),p}),e=this.shown[0].getBoundingClientRect(),r=this.doc.createElement("div");r.setAttribute("data-testid","anfra-overlay-label");let i=this.doc.defaultView?.innerHeight??0,o=e.top>=26?e.top-24:Math.max(4,e.top+4),d=Math.max(4,Math.min(e.left,(this.doc.defaultView?.innerWidth??0)-24));r.setAttribute("style",`${dt}left:${d}px;top:${Math.min(o,i-24)}px;`);for(let s of["kind","name","label","note","size"]){let a=this.text[s];if(!a)continue;let p=this.doc.createElement("span");p.setAttribute("style",ct[s]),p.setAttribute("data-part",s),p.textContent=a,r.appendChild(p)}this.root.replaceChildren(...n,r)}},_e=new WeakMap;function Q(t){let n=_e.get(t);return n||(n=new ie(t),_e.set(t,n)),n}function H(t){typeof t.scrollIntoView=="function"&&t.scrollIntoView({block:"center",inline:"nearest",behavior:"smooth"})}var oe=Object.freeze([]);function Re(t){return t instanceof Error&&t.name==="AbortError"}function $e(t,n){return t.controller!==n}function ut(t){return{name:t.name,message:t.message,...t.entity?{entity:t.entity}:{},...typeof t.status=="number"?{status:t.status}:{},...t.stack?{stack:t.stack}:{}}}function pt(t){let{result:n}=t;return{state:t.state,isDirty:t.isDirty,page:t.page,hasMore:t.hasMore,columns:n?n.columns.map(e=>({...e})):[],rowCount:n?.meta.numRows??0,selectedRowCount:t.selectedRows.length,signature:t.signature(),...t.error?{error:ut(t.error)}:{},...n?.debug?{debug:{...n.debug}}:{}}}function ft(t){let{options:n}=t;return{kind:t.kind,condition:{...t.condition},...t.appliedCondition?{appliedCondition:{...t.appliedCondition}}:{},isDirty:t.isDirty,...n?{options:[...n]}:{}}}function Oe(t){return{source:t.source,fields:[...t.fields],rowCount:t.rows.length,conditions:t.conditions.map(n=>({...n})),...t.expression?{expression:t.expression}:{},lossy:t.lossy}}function Te(t){return t?JSON.stringify({source:t.source,conditions:t.conditions,expression:t.expression}):"none"}function Fe(t,n){return t instanceof $?t:new F(t?.message??"The query failed.",n,t)}var W=class extends O{constructor(e,r,i,o){super();l(this,"title");l(this,"_timezone");l(this,"datasets");l(this,"backend");l(this,"features");l(this,"_queries",new Map);l(this,"_controls",new Map);l(this,"_mappings",[]);l(this,"_crossFilters",[]);l(this,"_selection");l(this,"_appliedSelection");l(this,"selectionCommitted",!1);l(this,"index",0);this.title=e.title,this._timezone=e.timezone,this.datasets=r,this.backend=i,this.features=o}get queries(){return Object.fromEntries(this._queries)}get controls(){return Object.fromEntries(this._controls)}get mappings(){return[...this._mappings,...this._crossFilters]}get crossFilters(){return this._crossFilters}createQuery(e,r){this.assertNameFree(e,"query");let i=new N(e,this,r);return this._queries.set(e,i),this.notify(),i}createFilter(e,r){this.assertNameFree(e,"filter");let i=r.dataset?this.datasetIndex(r.dataset,e):this.soleDataset(e,r.field),o=new J(e,this,r,i,this.backend);return this._controls.set(e,o),this.notify(),o}createDateDrill(e,r={}){if(this.features?.dateDrill===!1)throw new u(`Date drill \'${e}\' needs a capability this workspace has not enabled. Ask an admin to turn on \\`interactive_control:date_drill\\`.`,e);this.assertNameFree(e,"date drill control");let i=new j(e,this,r);return this._controls.set(e,i),this.notify(),i}mapControl(e,r,i){if(!(e instanceof I))throw new u("mapControl expects a control as its source. To let one query cross-filter another, use app.mapCrossFilter(from, to) \\u2014 a cross-filter edge names no field.");if(this._controls.get(e.name)!==e)throw new u(`Control \'${e.name}\' belongs to a different app.`,e.name);if(this._queries.get(r.name)!==r)throw new u(`Query \'${r.name}\' belongs to a different app.`,r.name);i.aggregation&&ue(i.aggregation,e.name);let o=new G(e,r,i.field,i.aggregation);if(this._mappings.some(d=>d.id===o.id))throw new u(`Mapping \'${o.id}\' is already declared.`,e.name);return this.assertAcyclic(o),this._mappings.push(o),this.notify(),o}mapCrossFilter(e,r){if(!(e instanceof N)||!(r instanceof N))throw new u("mapCrossFilter expects two queries. To let a control condition a query, use app.mapControl(from, to, { field }).");if(this._queries.get(e.name)!==e)throw new u(`Query \'${e.name}\' belongs to a different app.`,e.name);if(this._queries.get(r.name)!==r)throw new u(`Query \'${r.name}\' belongs to a different app.`,r.name);if(e===r)throw new u(`Query \'${e.name}\' cannot cross-filter itself. A source keeps its own rows so it can show what is selected; filtering it would erase the very rows the reader clicked.`,e.name);if(e.declaration.dataset!==r.declaration.dataset)throw new u(`Cross-filter \'${e.name}\' -> \'${r.name}\' spans two datasets (\'${e.declaration.dataset}\' and \'${r.declaration.dataset}\'). A selection carries field references, which mean nothing outside the dataset that resolved them.`,e.name);let i=new q(e,r);if(this._crossFilters.some(o=>o.id===i.id))throw new u(`Cross-filter \'${i.id}\' is already declared.`,e.name);return this._crossFilters.push(i),this.notify(),i}get selection(){return this._selection}get appliedSelection(){return this._appliedSelection}clearSelection(){this._selection&&(this._selection=void 0,this.notify())}setTimezone(e){this._timezone=e,this.notify()}get timezone(){return this._timezone}datasetIndex(e,r){return ce(this.datasets,e,r)}mappingsTo(e){return this._mappings.filter(r=>r.to.name===e)}selectionExpressionFor(e,r){return this.selectionFor(e,r)?.expression}selectionFor(e,r){let i=r==="pending"?this._selection:this._appliedSelection;return!i||i.source===e?void 0:this._crossFilters.some(d=>d.from.name===i.source&&d.to.name===e)?i:void 0}selectionConditionsFor(e,r){let i=r==="pending"?this._selection:this._appliedSelection;return!i||i.source===e?oe:this._crossFilters.some(d=>d.from.name===i.source&&d.to.name===e)?i.conditions:oe}drillTransformations(e){let r={};return this._mappings.forEach(i=>{if(i.to!==e)return;let o=i.from.appliedCondition;if(o?.operator!=="transform_date_drill")return;let[d]=o.values??[];typeof d=="string"&&(r[i.field]=d)}),r}select(e,r,i){if(this._queries.get(e.name)!==e)throw new u(`Query \'${e.name}\' belongs to a different app.`,e.name);let o=Se(e,r,i,this.drillTransformations(e));o?.lossy&&console.warn(`[anfra-sdk] The selection in \'${e.name}\' filters more broadly than it was made. Its conditions are combined field by field and ANDed, which cannot express "(A and B) or (C and D)", so the targets will also show combinations that were not selected. Select within one dimension, or narrow with \\`fields\\`, to avoid this.`),this._selection=o,this.notify()}get hasChanges(){return[...this._controls.values()].some(e=>e.isDirty)||this.isSelectionDirty}get isSelectionDirty(){return this.selectionCommitted?Te(this._selection)!==Te(this._appliedSelection):!1}async execute(e={}){let r=[...this._queries.values()].filter(s=>s.shouldRun(e.force));this._controls.forEach(s=>s.commit()),this._appliedSelection=this._selection,this.selectionCommitted=!0,r.forEach(s=>{s.controller?.abort(),s.resetPage()});let i={backend:this.backend,timezone:this._timezone,bustCache:e.bustCache},o=[],d=[];return await Promise.all(r.map(async s=>{let a=new AbortController;s.controller=a;let p=s.signature();s.markExecuting();try{let b=await ee(s,this.mappingsTo(s.name),{conditions:this.selectionConditionsFor(s.name,"applied"),expression:this.selectionExpressionFor(s.name,"applied")},i,a.signal);if($e(s,a))return;s.markSuccess(b,p,!1),o.push(s.name)}catch(b){if($e(s,a)||Re(b))return;s.markError(Fe(b,s.name)),d.push(s.name)}finally{s.controller===a&&(s.controller=void 0)}})),{succeeded:o,failed:d}}async refresh(){return this.execute({force:!0,bustCache:!0})}abort(){this._queries.forEach(e=>e.abort())}async fetchPage(e){let r=new AbortController;e.controller=r;let i=e.signature();e.markExecuting();try{let o=await ee(e,this.mappingsTo(e.name),{conditions:this.selectionConditionsFor(e.name,"applied"),expression:this.selectionExpressionFor(e.name,"applied")},{backend:this.backend,timezone:this._timezone},r.signal);e.markSuccess(o,i,!0)}catch(o){if(e.rewindPage(),Re(o))return;let d=Fe(o,e.name);throw e.markError(d),d}finally{e.controller===r&&(e.controller=void 0)}}toJSON(){let e=r=>Object.fromEntries([...this._controls.entries()].filter(([,i])=>i.kind===r).map(([i,o])=>[i,o.toJSON()]));return{...this.title?{title:this.title}:{},...this._timezone?{timezone:this._timezone}:{},queries:Object.fromEntries([...this._queries.entries()].map(([r,i])=>[r,i.toJSON()])),filters:e("filter"),dateDrills:e("dateDrill"),mappings:[...this._mappings.map(r=>r.toJSON()),...this._crossFilters.map(r=>r.toJSON())]}}toInspectJSON(){return{...this.title?{title:this.title}:{},...this._timezone?{timezone:this._timezone}:{},hasChanges:this.hasChanges,declaration:this.toJSON(),queries:Object.fromEntries([...this._queries.entries()].map(([e,r])=>[e,pt(r)])),controls:Object.fromEntries([...this._controls.entries()].map(([e,r])=>[e,ft(r)])),...this._selection?{selection:Oe(this._selection)}:{},...this._appliedSelection?{appliedSelection:Oe(this._appliedSelection)}:{}}}locate(e,r){let i=globalThis.document;if(!i)return!1;let o=Ce(i,e,this.index,r);return o.length?(H(o[0]),Q(i).flash(o,{kind:e,name:r,...o.length>1?{note:`in ${o.length} places`}:{}}),!0):!1}assertNameFree(e,r){pe([...this._queries.keys(),...this._controls.keys()],e,r)}soleDataset(e,r){if(!r)return;let i=Object.keys(this.datasets);if(i.length===1)return this.datasets[i[0]];throw new u(i.length?`Filter \'${e}\' names a field but the environment has ${i.length} datasets. Say which one: ${i.join(", ")}.`:`Filter \'${e}\' names a field but the environment has no datasets.`,e)}assertAcyclic(e){let r=[...this._mappings,e],i=new Map;r.forEach(a=>{let p=a.from.name;i.set(p,[...i.get(p)??[],a.to.name])});let o=new Set,d=new Set,s=a=>{if(o.has(a))return!0;if(d.has(a))return!1;o.add(a);let p=(i.get(a)??[]).some(s);return o.delete(a),d.add(a),p};if([...i.keys()].some(s))throw new u(`Mapping \'${e.id}\' would make the app\'s mappings cyclic.`,e.from.name)}};var Ne="0.6.1";var ht=Ne,se=class{constructor(n){l(this,"version",ht);l(this,"_apps",[]);l(this,"datasets");l(this,"user");l(this,"indices");l(this,"backend");l(this,"features");if(!n?.datasets)throw new u("No datasets provided. The host must call createSdk({ datasets }) before an app can be declared.");if(!n.user)throw new u("No user provided. The host must call createSdk({ user }) before an app can be declared.");if(!n.backend)throw new u("No backend provided. The host must call createSdk({ backend }) before an app can run a query.");this.datasets=Object.freeze({...n.datasets}),this.user=Object.freeze({...n.user,permissions:Object.freeze({...n.user.permissions})}),this.indices=Object.fromEntries(Object.entries(n.datasets).map(([e,r])=>[e,new U(r)])),this.backend=n.backend,this.features=Object.freeze({...n.features})}createApp(n={}){let e=new W(n,this.indices,this.backend,this.features);return e.index=this._apps.length,this._apps.push(e),e}get apps(){return this._apps}};function Ie(t){return new se(t)}var B="Anfra";function Me(t,n=globalThis){let e=n[B],r=B in n;return n[B]=t,()=>{r?n[B]=e:delete n[B]}}function Qe(t,n){let e=null,r=a=>a.target instanceof Element?a.target.closest(K):null,i=a=>{let p=r(a);p!==e&&(e=p,n.onHover(p))},o=a=>{a.preventDefault(),a.stopPropagation();let p=r(a);p&&n.onPick(p)},d=a=>{a.key==="Escape"&&n.onCancel()},s={capture:!0};return t.addEventListener("mousemove",i,s),t.addEventListener("click",o,s),t.addEventListener("keydown",d,s),()=>{t.removeEventListener("mousemove",i,s),t.removeEventListener("click",o,s),t.removeEventListener("keydown",d,s)}}function Le(){let t=new Error("Aborted");return t.name="AbortError",t}function gt(t){switch(t?.name){case"PermissionError":return new P(t.message);case"TransportError":return new V(t.message);default:return new F(t?.message||"The query failed.")}}function Be(t){let n=t.document.getElementById(ae);if(!n?.textContent)throw new Error("anfra: this frame was not provisioned by a host.");let e=JSON.parse(n.textContent),r=c=>t.parent.postMessage(c,e.hostOrigin),i=new Map,o=1,d=!1,s;t.addEventListener("message",c=>{if(c.source!==t.parent)return;let m=c.data;switch(m?.type){case"anfra:response":{let g=i.get(m.id);if(!g)return;i.delete(m.id),m.ok?g.resolve(m.result):g.reject(gt(m.error));return}case"anfra:inspect-watch":T(m.open);return;case"anfra:highlight":D(m.node);return;case"anfra:locate":k(m.target);return;case"anfra:pick-watch":R(m.on);return;default:}});function a(c,m,g){return new Promise((A,X)=>{if(g.aborted){X(Le());return}let z=o++;i.set(z,{resolve:A,reject:X}),g.addEventListener("abort",()=>{i.delete(z)&&(r({type:"anfra:cancel",id:z}),X(Le()))},{once:!0}),r({type:"anfra:request",id:z,method:c,request:m})})}let p={submitQuery:(c,m)=>a("submitQuery",c,m).then(g=>(g.debug?.executedAt&&(g.debug.executedAt=new Date(g.debug.executedAt)),g)),fieldSuggestions:(c,m)=>a("fieldSuggestions",c,m)},b=Ie({datasets:e.datasets,user:e.user,backend:p}),S=t.document,f,v="",x,E;function h(){if(s=void 0,!!d)try{f=re(S,b.apps),v=JSON.stringify(f.nodes),r({type:"anfra:inspect",apps:b.apps.map(c=>c.toInspectJSON()),structure:{nodes:f.nodes,usage:f.usage}})}catch{}}function y(){d&&s===void 0&&(s=setTimeout(h,50))}function _(){let c=t.MutationObserver;if(x||typeof c!="function")return;let m=new c(()=>{if(!(!d||s!==void 0))try{JSON.stringify(re(S,b.apps).nodes)!==v&&y()}catch{}});x=m,m.observe(S.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-anfra-container","data-anfra-block","data-anfra-query","data-anfra-control","data-anfra-label"]})}function T(c){d=c,c?(_(),h()):(x?.disconnect(),x=void 0,R(!1),Q(S).hide(),f=void 0,v="")}function w(c,m){for(let g of c){if(g.key===m)return g;let A=w(g.children,m);if(A)return A}}function D(c){let m=Q(S),g=c!==null&&f?w(f.nodes,c):void 0,A=c!==null?f?.elements.get(c):void 0;if(!g||!A||!f){m.hide();return}m.show([A],De(g,A,f))}function k(c){if("node"in c){let m=f?.elements.get(c.node);m&&H(m),D(c.node)}else b.apps[c.app]?.locate(c.kind,c.name);y()}function R(c){if(!c){E?.(),E=void 0;return}if(E)return;let m=g=>f?.keysOf.get(g)?.[0];E=Qe(S,{onHover:g=>D(g?m(g)??null:null),onPick:g=>{let A=m(g);A!==void 0&&r({type:"anfra:picked",node:A})},onCancel:()=>{R(!1),Q(S).hide(),r({type:"anfra:picked",node:null})}})}let Y=b.createApp.bind(b);b.createApp=c=>{let m=Y(c);return m.subscribe(y),y(),m},Me(b,t),r({type:"anfra:ready"})}Be(window);})();\n';
74
+ var anfra_sdk_frame_script_default = '"use strict";(()=>{var ze=Object.defineProperty;var Pe=(t,n,e)=>n in t?ze(t,n,{enumerable:!0,configurable:!0,writable:!0,value:e}):t[n]=e;var d=(t,n,e)=>Pe(t,typeof n!="symbol"?n+"":n,e);var $=class extends Error{constructor(e,r){super(e);d(this,"entity");this.name=new.target.name,this.entity=r,Object.setPrototypeOf(this,new.target.prototype)}},p=class extends ${},T=class extends ${constructor(e,r,i){super(e,r);d(this,"cause");this.cause=i}},P=class extends ${},V=class extends ${constructor(e,r,i){super(e,i);d(this,"status");this.status=r}};var ae="anfra-provision";var O=class{constructor(n){d(this,"listeners",new Set);d(this,"parent");this.parent=n}subscribe(n){return this.listeners.add(n),()=>{this.listeners.delete(n)}}notifyChange(){this.listeners.forEach(n=>n()),this.parent?.notifyChange()}notify(){this.notifyChange()}};var F=class extends O{constructor(e,r,i){super(r);d(this,"kind","query");d(this,"name");d(this,"declaration");d(this,"dataset");d(this,"app");d(this,"_state","idle");d(this,"_result");d(this,"_error");d(this,"_page",1);d(this,"_sort",[]);d(this,"lastRunSignature");d(this,"lastPageRows",0);d(this,"controller");if(!i.aql?.trim())throw new p(`Query \'${e}\' declares no AQL.`,e);let{pageSize:s}=i;if(s!==void 0&&!(Number.isInteger(s)&&s>=1))throw new p(`Query \'${e}\' declares pageSize ${s}; it must be a whole number of at least 1.`,e);this.name=e,this.app=r,this.declaration=i,this.dataset=r.datasetIndex(i.dataset,e)}locate(){return this.app.locate("query",this.name)}get state(){return this._state}get result(){return this._result}get error(){return this._error}get pageSize(){return this.declaration.pageSize}get page(){return this._page}get sort(){return this._sort}setSort(e){this._sort=[...e],this.notify()}get hasMore(){return!this._result||this.pageSize===void 0?!1:this.lastPageRows>=this.pageSize}signature(){let e=this.app.mappingsTo(this.name).map(r=>({field:r.field,aggregation:r.aggregation,condition:r.from.condition})).sort((r,i)=>r.field.localeCompare(i.field)||JSON.stringify(r).localeCompare(JSON.stringify(i)));return JSON.stringify({declaration:this.declaration,conditions:e,selection:this.app.selectionConditionsFor(this.name,"pending"),selectionExpression:this.app.selectionExpressionFor(this.name,"pending"),sort:this._sort,timezone:this.app.timezone,page:this._page})}get isDirty(){return this.lastRunSignature!==void 0&&this.signature()!==this.lastRunSignature}shouldRun(e=!1){return e||this.lastRunSignature===void 0||this.isDirty}abort(){this.controller?.abort()}async fetchMore(){if(this.pageSize===void 0)throw new p(`Query \'${this.name}\' declares no pageSize, so it has no more pages.`,this.name);this.hasMore&&(this._page+=1,await this.app.fetchPage(this))}select(e,r={}){this.app.select(this,Array.isArray(e)?e:[e],r.fields)}get selectedRows(){let{selection:e}=this.app;if(e?.source!==this.name)return[];let r=this._result?.rows;if(!r)return e.rows;let i=l=>JSON.stringify(e.fields.map(o=>l[o]??null)),s=new Set(e.rows.map(i));return r.filter(l=>s.has(i(l)))}markExecuting(){this._state="executing",this._error=void 0,this.notify()}markSuccess(e,r,i){if(this._state="success",this._error=void 0,this.lastRunSignature=r,this.lastPageRows=e.rows.length,i&&this._result){let s=[...this._result.rows,...e.rows];this._result={...e,rows:s,meta:{...e.meta,numRows:s.length}}}else this._result=e;this.notify()}markError(e){this._state="error",this._error=e,this.notify()}rewindPage(){this._page>1&&(this._page-=1)}resetPage(){this._page=1,this.lastPageRows=0}toJSON(){return this.declaration}};var le=["year","quarter","month","week","day","hour","minute"];function Ve(t,n){if(!le.includes(n))throw new p(`Invalid grain \'${n}\'. Expected one of: ${le.join(", ")}.`,t)}function de(t,n){return n?(Ve(t,n),{operator:"transform_date_drill",values:[`datetrunc ${n}`]}):{operator:"none"}}function Je(t){let n=t.toLowerCase();return n.includes("date")||n.includes("time")?"date":n.includes("int")||n.includes("number")||n.includes("float")||n.includes("decimal")?"number":n.includes("bool")?"boolean":"string"}function je(t,n){return JSON.stringify(t??null)===JSON.stringify(n??null)}var I=class extends O{constructor(e,r,i){super(r);d(this,"name");d(this,"app");d(this,"pending");d(this,"applied");d(this,"initial");this.name=e,this.app=r,this.initial=i,this.pending=i}locate(){return this.app.locate("control",this.name)}get condition(){return this.pending}get appliedCondition(){return this.applied}get isDirty(){return this.applied!==void 0&&!je(this.pending,this.applied)}setCondition(e){this.pending=e,this.notify()}reset(){this.setCondition(this.initial)}commit(){this.applied=this.pending}},J=class extends I{constructor(e,r,i,s,l){super(e,r,i.default??{operator:"is",values:[]});d(this,"kind","filter");d(this,"declaration");d(this,"type");d(this,"resolved");d(this,"datasetName");d(this,"backend");d(this,"loadedOptions");if(this.declaration=i,this.backend=l,i.field){if(!s)throw new p(`Filter \'${e}\' names a field but no dataset. Pass \\`dataset\\` alongside \\`field\\`.`,e);this.resolved=s.resolve(i.field,e),this.datasetName=s.descriptor.name,this.type=i.type??Je(this.resolved.type)}else{if(!i.type)throw new p(`Filter \'${e}\' is a manual filter, so it needs a \\`type\\`.`,e);this.type=i.type}this.assertValues(this.condition.values??[]),this.assertValues(i.options??[])}setCondition(e){this.assertValues(e.values??[]),super.setCondition(e)}assertValues(e){if(this.type!=="number")return;let r=e.find(i=>typeof i=="number");if(r!==void 0)throw new p(`Filter \'${this.name}\' filters numbers: give its values as strings, as query results give them (${JSON.stringify(String(r))}, not ${String(r)}), so none has lost a digit.`,this.name)}get isFieldBacked(){return this.resolved!==void 0}get options(){return this.loadedOptions??this.declaration.options??[]}async loadOptions(e,r=new AbortController().signal){let i=this.resolved?.modelName;if(!this.resolved||!i||!this.backend||!this.datasetName)return this.options;let s=await this.backend.fieldSuggestions({dataset:this.datasetName,model:i,field:this.resolved.fieldName,q:e??""},r);return this.loadedOptions=[...s??[]],this.notify(),this.loadedOptions}toJSON(){return this.declaration}},j=class extends I{constructor(e,r,i={}){super(e,r,de(e,i.default));d(this,"kind","dateDrill");d(this,"declaration");this.declaration=i}setGrain(e){this.setCondition(de(this.name,e))}toJSON(){return this.declaration}};var G=class{constructor(n,e,r,i){d(this,"kind","control");d(this,"from");d(this,"to");d(this,"field");d(this,"aggregation");this.from=n,this.to=e,this.field=r,this.aggregation=i}get id(){return`${this.from.name}.${this.to.name}.${this.field}`}toJSON(){return{kind:this.kind,id:this.id,from:{kind:this.from.kind,name:this.from.name},to:{kind:"query",name:this.to.name},field:this.field,...this.aggregation?{aggregation:this.aggregation}:{}}}},q=class{constructor(n,e){d(this,"kind","crossFilter");d(this,"from");d(this,"to");this.from=n,this.to=e}get id(){return`${this.from.name}.${this.to.name}`}toJSON(){return{kind:this.kind,id:this.id,from:{kind:"query",name:this.from.name},to:{kind:"query",name:this.to.name}}}};var Z=["sum","avg","max","min","count","count distinct","median","stdev","stdevp","var","varp"];function Ge(t,n){let e=t.length+1,r=n.length+1,i=Array.from({length:r},(s,l)=>l);for(let s=1;s<e;s++){let l=[s];for(let o=1;o<r;o++)l[o]=Math.min(i[o]+1,l[o-1]+1,i[o-1]+(t[s-1]===n[o-1]?0:1));i=l}return i[r-1]}function qe(t,n){let e,r=1/0;n.forEach(s=>{let l=Ge(t.toLowerCase(),s.toLowerCase());l<r&&(r=l,e=s)});let i=Math.max(2,Math.floor(t.length/3));return r<=i?e:void 0}function M(t,n,e){let r=qe(n,e);return r?`${t} Did you mean \'${r}\'?`:t}var U=class{constructor(n){d(this,"descriptor");d(this,"fields",new Map);d(this,"references");this.descriptor=n,n.data_models.forEach(e=>{e.fields.forEach(r=>{let i=String(e.name);this.fields.set(`${i}.${r.name}`,{modelName:i,fieldName:r.name,isMetric:!1,type:r.type,label:r.label??r.name})})}),n.metrics.forEach(e=>{this.fields.set(e.name,{fieldName:e.name,isMetric:!0,type:e.type,label:e.label??e.name})}),this.references=[...this.fields.keys()]}resolve(n,e){let r=this.fields.get(n);if(r)return r;throw new p(M(`Unknown field \'${n}\' on dataset \'${this.descriptor.name}\'.`,n,this.references),e)}};function ce(t,n,e){let r=t[n];if(r)return r;throw new p(M(`Unknown dataset \'${n}\'.`,n,Object.keys(t)),e)}function ue(t,n){if(!Z.includes(t))throw new p(M(`Invalid filter aggregation \'${t}\'.`,t,[...Z]),n)}function pe(t,n,e){if(!n)throw new p(`A ${e} needs a name.`);for(let r of t)if(r===n)throw new p(`\'${n}\' is already declared in this app.`,n)}function Ue(t,n){if(!(!n||n.operator==="none"||n.operator==="transform_date_drill"||!["is_null","not_null","is_true","is_false","transform_pop_none"].includes(n.operator)&&!n.values?.length))return{field:t.field,operator:n.operator,values:n.values??[],...n.modifier?{modifier:n.modifier}:{},...t.aggregation?{aggregation:t.aggregation}:{}}}function Ke(t,n){if(n?.operator!=="transform_date_drill")return;let e=n.values?.[0];if(typeof e!="string")return;let r=e.replace(/^datetrunc\\s+/,"");return{field:t.field,grain:r}}function He(t,n){let e=n.columns??[],r=(n.values??[]).map(s=>{let l={};return e.forEach((o,a)=>{l[o.name]=s[a]}),l}),{pageSize:i}=t;return{columns:e,rows:r,meta:{...i!==void 0?{page:n.meta?.page??t.page,pageSize:n.meta?.pageSize??i}:{},numRows:n.meta?.numRows??r.length},...n.debug?{debug:n.debug}:{}}}function We(t,n,e,r){let i=n.map(a=>Ue(a,a.from.appliedCondition)).filter(a=>a!==void 0),s=n.map(a=>Ke(a,a.from.appliedCondition)).filter(a=>a!==void 0),l=e.conditions.map(a=>({field:a.field,operator:a.operator,values:a.values})),o=e.expression?[{expr:e.expression}]:[];return{dataset:t.dataset.descriptor.name,aql:t.declaration.aql,input:{filters:[...i,...l],conditions:o,sorts:t.sort.map(a=>({field:a.field,direction:a.direction??"asc"})),dateDrills:s},...t.pageSize!==void 0?{page:t.page,pageSize:t.pageSize}:{},...r.timezone?{timezone:r.timezone}:{},...r.bustCache?{bustCache:!0}:{}}}async function ee(t,n,e,r,i){let s=We(t,n,e,r),l=await r.backend.submitQuery(s,i);return He(t,l)}function Ye(t,n){if(t===null)return"null";if(typeof t=="boolean")return t?"true":"false";if(n==="number"&&typeof t=="string")return et(t);if(typeof t=="number"){if(!Number.isFinite(t))throw new RangeError(`Cannot express ${t} in AQL.`);return Math.abs(t)>=1e21?BigInt(t).toString():String(t)}return JSON.stringify(t).split("\\u2028").join("\\\\u2028").split("\\u2029").join("\\\\u2029")}function fe(t,n,e){return n===null?`is(${t}, null)`:`${t} is ${Ye(n,e)}`}var Xe=new Set(["NaN","Infinity","-Infinity"]),Ze=/^(-?)(0|[1-9]\\d*)(?:\\.(\\d+))?(?:[eE]([+-]?\\d+))?$/;function et(t){let n=Ze.exec(t);if(!n)throw Xe.has(t)?new RangeError(`Cannot express ${t} in AQL.`):new TypeError(`${JSON.stringify(t)} is not a number: a number is a string in JSON\'s number syntax.`);let[,e,r,i="",s]=n;if(s===void 0)return t;let l=r+i,o=r.length+Number(s),a;return o<=0?a=`0.${"0".repeat(-o)}${l}`:o>=l.length?a=l+"0".repeat(o-l.length):a=`${l.slice(0,o)}.${l.slice(o)}`,e+a.replace(/^0+(?=\\d)/,"")}function me(t,n,e){return`${t} matches @(${n} until ${e})`}function he(t,n){if(n.length===0)throw new RangeError(`\\`${t}\\` needs at least one operand.`);return n.length===1?n[0]:`${t}(${n.join(", ")})`}var ge=t=>he("and",t),ye=t=>he("or",t);var tt=/^(\\d{4})-(\\d{2})-(\\d{2})(?:[T ](\\d{2}):(\\d{2})(?::(\\d{2}))?)?/,nt={week:7*864e5,day:864e5,hour:36e5,minute:6e4};function be(t){if(typeof t!="string")return;let n=tt.exec(t);if(n)return{year:Number(n[1]),month:Number(n[2])-1,day:Number(n[3]),hour:Number(n[4]??0),minute:Number(n[5]??0),second:Number(n[6]??0),hasTime:n[4]!==void 0}}function L(t,n){let e=new Date(t),r=s=>String(s).padStart(2,"0"),i=`${e.getUTCFullYear()}-${r(e.getUTCMonth()+1)}-${r(e.getUTCDate())}`;return n?`${i} ${r(e.getUTCHours())}:${r(e.getUTCMinutes())}:${r(e.getUTCSeconds())}`:i}function we(t){return Date.UTC(t.year,t.month,t.day,t.hour,t.minute,t.second)}function te(t){let n=be(t);if(n)return L(we(n),n.hasTime)}function ne(t,n){let e=be(t);if(!e)return;let{year:r,month:i,day:s,hour:l,minute:o,second:a,hasTime:c}=e;return L(n==="year"?Date.UTC(r+1,i,s,l,o,a):n==="quarter"?Date.UTC(r,i+3,s,l,o,a):n==="month"?Date.UTC(r,i+1,s,l,o,a):we(e)+nt[n],c)}function ke(t){if(t?.startsWith("datetrunc "))return t.slice(10)}function rt(t){let n=new Set;return t.filter(e=>{let r=`${typeof e}:${String(e)}`;return n.has(r)?!1:(n.add(r),!0)})}function it(t,n){let e=t.map(te);if(e.some(i=>i===void 0))return!1;let r=e.slice().sort();for(let i=1;i<r.length;i+=1)if(ne(r[i-1],n)!==r[i])return!1;return!0}function st(t,n,e){let r=n.map(te);if(r.some(a=>a===void 0))return;let i=r.slice().sort(),s=i[0],l=ne(i[i.length-1],e);if(!l)return;let o=it(n,e);return{condition:{field:t,operator:"matches",values:[`${s} until ${l}`]},admits:o?n.length:Number.POSITIVE_INFINITY,lossy:!o}}function ot(t,n,e){let r=e.filter(o=>o!=null),i=rt(r);if(!i.length)return{condition:{field:t,operator:"is_null",values:[]},admits:1,lossy:!1};let s=r.length!==e.length,l=ke(n);if(l){let o=st(t,i,l);if(o)return{...o,lossy:o.lossy||s}}return{condition:{field:t,operator:"is",values:i},admits:i.length,lossy:s}}function ve(t){return t.modelId?`${t.modelId}.${t.fieldName}`:t.fieldName}function at(t,n,e,r){try{let i=e.map(s=>ge(n.map(l=>{let o=t.get(l),a=ve(o),c=s[l]??null,b=ke(r[a]);if(b&&typeof c=="string"){let S=te(c),u=ne(c,b);if(S&&u)return me(a,S,u)}if(c!==null&&typeof c!="string"&&typeof c!="number"&&typeof c!="boolean")throw new TypeError("no AQL spelling");return fe(a,c,o.semantic_type)})));return ye(i)}catch{return}}function Se(t,n,e,r={}){let i=t.result?.columns??[],s=new Map(i.map(h=>[h.name,h]));e?.forEach(h=>{let y=s.get(h);if(y){if(y.adhoc)throw new p(`Query \'${t.name}\' cannot cross-filter on \'${h}\': it is a query-local AQL expression, which only this query\'s result carries. The target query would receive a condition naming a field it has never heard of.`,t.name);if(y.isMeasure)throw new p(`Query \'${t.name}\' cannot cross-filter on \'${h}\': it is a measure, and a selection conditions dimensions only.`,t.name);return}throw new p(M(`Query \'${t.name}\' has no column \'${h}\' in its last result.`,h,[...s.keys()]),t.name)});let l=i.filter(h=>!h.isMeasure),o=l.filter(h=>!h.adhoc).map(h=>h.name),a=e?[...e]:o;if(!n.length||!a.length)return;let c=[],b=1,S=!e&&o.length!==l.length,u=!1;a.forEach(h=>{let y=s.get(h),A=ve(y),N=ot(A,r[A],n.map(w=>w[h]));c.push(N.condition),b*=N.admits,u=u||N.lossy});let v=new Set(n.map(h=>JSON.stringify(a.map(y=>h[y]??null))));b>v.size&&(u=!0);let x=c.sort((h,y)=>h.field.localeCompare(y.field)),E={source:t.name,rows:[...n],fields:a};if(u){let h=at(s,a,n,r);if(h)return{...E,conditions:[],expression:h,lossy:S}}return{...E,conditions:x,lossy:S||u}}var C={container:"data-anfra-container",block:"data-anfra-block",query:"data-anfra-query",control:"data-anfra-control",label:"data-anfra-label"},K=`[${C.container}],[${C.block}],[${C.query}],[${C.control}]`;function xe(t){let n=/^(?:(\\d+)\\/)?([^/\\s]+)$/.exec(t);if(n)return{app:n[1]===void 0?0:Number(n[1]),name:n[2]}}var Ee=t=>(t??"").split(/\\s+/).filter(Boolean);function Ce(t,n,e,r){let i=n==="query"?C.query:C.control;return[...t.querySelectorAll(`[${i}]`)].filter(s=>Ee(s.getAttribute(i)).some(l=>{let o=xe(l);return o?.app===e&&o.name===r}))}var lt=(t,n,e)=>{let r=t[e.app];return r?Object.prototype.hasOwnProperty.call(n==="query"?r.queries:r.controls,e.name):!1};function re(t,n){var S;let e=[],r=new Map,i=new Map,s=new Map,l=new Map,o={container:new Set,block:new Set},a={},c=(u,v,x)=>{let E=u?s.get(u).children:e,h=u?`${s.get(u).key}.`:"",y={...v,key:`${h}${E.length}`,children:[]};return E.push(y),r.set(y.key,x),i.set(x,[...i.get(x)??[],y.key]),y};for(let u of t.querySelectorAll(K)){let v=u.parentElement?.closest(K)??null,x=u.getAttribute(C.container),E=u.getAttribute(C.block),h=u.getAttribute(C.label)??void 0,y=v&&(l.get(v)==="block"?v:v.closest(`[${C.block}]`)),A;if(x!==null||E!==null){let w=x!==null?"container":"block",D=(x??E??"").trim(),k=[];x!==null&&E!==null&&k.push("This element is marked as a container and as a block. It counts as a container."),D?o[w].has(D)?k.push(`Another ${w} already has the id "${D}".`):o[w].add(D):k.push(`This ${w} has no id.`),y&&k.push(`A block can\'t hold a ${w}. This one is inside the block "${y.getAttribute(C.block)}".`),A=c(v,{kind:w,id:D,...h!==void 0?{label:h}:{},problems:k},u),s.set(u,A),l.set(u,w)}let N=A?u:v;for(let w of["query","control"])for(let D of Ee(u.getAttribute(w==="query"?C.query:C.control))){let k=xe(D),R=[];if(k?lt(n,w,k)||R.push(n[k.app]?`No ${w} is called "${k.name}" in app ${k.app}.`:`The page has no app ${k.app}.`):R.push(`Can\'t read "${D}". Write the ${w}\'s name, or app/name when the page has several apps.`),c(N,{kind:w,name:k?.name??D,app:k?.app??0,problems:R},u),k&&!R.length){let Y=`${k.app}/${k.name}`,f=a[S=`${w}:${Y}`]??(a[S]={elements:new Set,blocks:new Set});f.elements.add(u);let m=A?.kind==="block"?A.id:y?.getAttribute(C.block);m&&f.blocks.add(m)}}}let b=Object.fromEntries(Object.entries(a).map(([u,v])=>[u,{markers:v.elements.size,blocks:[...v.blocks]}]));return{nodes:e,usage:b,elements:r,keysOf:i}}var dt=(t,n,e)=>`${t}:${n}/${e}`;function ct(t){let n=t.getBoundingClientRect();return`${Math.round(n.width)} \\xD7 ${Math.round(n.height)}`}function De(t,n,e){let r=ct(n);if(t.kind==="container"||t.kind==="block")return{kind:t.kind,name:t.id??"",...t.label?{label:t.label}:{},size:r};let i=e.usage[dt(t.kind,t.app??0,t.name??"")];return{kind:t.kind,name:t.name??"",...i&&i.markers>1?{note:`1 of ${i.markers}`}:{},size:r}}var _e="anfra-overlay";var ut="position:fixed;box-sizing:border-box;background:rgba(37,99,235,0.22);outline:1px solid #2563eb;outline-offset:-1px;",pt="position:fixed;max-width:60vw;display:flex;gap:6px;align-items:baseline;padding:3px 8px;border-radius:4px;background:#0f172a;color:#f8fafc;font:12px/1.4 system-ui,sans-serif;white-space:nowrap;box-shadow:0 2px 8px rgba(0,0,0,0.35);",ft={kind:"color:#94a3b8;",name:"color:#93c5fd;font-weight:700;",label:"color:#f8fafc;",note:"color:#94a3b8;",size:"color:#94a3b8;margin-left:10px;"},ie=class{constructor(n){d(this,"doc",n);d(this,"root");d(this,"shown",[]);d(this,"text",{kind:"",name:""});d(this,"timer");d(this,"reposition",()=>this.draw());let e=n.getElementById(_e);e?this.root=e:(this.root=n.createElement("div"),this.root.id=_e,this.root.setAttribute("aria-hidden","true"),this.root.setAttribute("style","position:fixed;inset:0;z-index:2147483647;pointer-events:none;display:none;"),(n.body??n.documentElement).appendChild(this.root))}show(n,e){if(this.clearTimer(),this.shown=[...n],this.text=e,!this.shown.length){this.hide();return}this.root.style.display="block",this.draw(),this.doc.addEventListener("scroll",this.reposition,{capture:!0,passive:!0}),this.doc.defaultView?.addEventListener("resize",this.reposition)}flash(n,e,r=1500){this.show(n,e),this.timer=setTimeout(()=>this.hide(),r)}hide(){this.clearTimer(),this.shown=[],this.root.style.display="none",this.root.replaceChildren(),this.doc.removeEventListener("scroll",this.reposition,{capture:!0}),this.doc.defaultView?.removeEventListener("resize",this.reposition)}get visible(){return this.shown.length>0}clearTimer(){this.timer!==void 0&&clearTimeout(this.timer),this.timer=void 0}draw(){let n=this.shown.map(o=>{let a=o.getBoundingClientRect(),c=this.doc.createElement("div");return c.setAttribute("style",`${ut}left:${a.left}px;top:${a.top}px;width:${a.width}px;height:${a.height}px;`),c}),e=this.shown[0].getBoundingClientRect(),r=this.doc.createElement("div");r.setAttribute("data-testid","anfra-overlay-label");let i=this.doc.defaultView?.innerHeight??0,s=e.top>=26?e.top-24:Math.max(4,e.top+4),l=Math.max(4,Math.min(e.left,(this.doc.defaultView?.innerWidth??0)-24));r.setAttribute("style",`${pt}left:${l}px;top:${Math.min(s,i-24)}px;`);for(let o of["kind","name","label","note","size"]){let a=this.text[o];if(!a)continue;let c=this.doc.createElement("span");c.setAttribute("style",ft[o]),c.setAttribute("data-part",o),c.textContent=a,r.appendChild(c)}this.root.replaceChildren(...n,r)}},Ae=new WeakMap;function Q(t){let n=Ae.get(t);return n||(n=new ie(t),Ae.set(t,n)),n}function H(t){typeof t.scrollIntoView=="function"&&t.scrollIntoView({block:"center",inline:"nearest",behavior:"smooth"})}var se=Object.freeze([]);function Re(t){return t instanceof Error&&t.name==="AbortError"}function $e(t,n){return t.controller!==n}function mt(t){return{name:t.name,message:t.message,...t.entity?{entity:t.entity}:{},...typeof t.status=="number"?{status:t.status}:{},...t.stack?{stack:t.stack}:{}}}function ht(t){let{result:n}=t;return{state:t.state,isDirty:t.isDirty,page:t.page,hasMore:t.hasMore,columns:n?n.columns.map(e=>({...e})):[],rowCount:n?.meta.numRows??0,selectedRowCount:t.selectedRows.length,signature:t.signature(),...t.error?{error:mt(t.error)}:{},...n?.debug?{debug:{...n.debug}}:{}}}function gt(t){let{options:n}=t;return{kind:t.kind,condition:{...t.condition},...t.appliedCondition?{appliedCondition:{...t.appliedCondition}}:{},isDirty:t.isDirty,...n?{options:[...n]}:{}}}function Oe(t){return{source:t.source,fields:[...t.fields],rowCount:t.rows.length,conditions:t.conditions.map(n=>({...n})),...t.expression?{expression:t.expression}:{},lossy:t.lossy}}function Ne(t){return t?JSON.stringify({source:t.source,conditions:t.conditions,expression:t.expression}):"none"}function Te(t,n){return t instanceof $?t:new T(t?.message??"The query failed.",n,t)}var W=class extends O{constructor(e,r,i,s){super();d(this,"title");d(this,"_timezone");d(this,"datasets");d(this,"backend");d(this,"features");d(this,"_queries",new Map);d(this,"_controls",new Map);d(this,"_mappings",[]);d(this,"_crossFilters",[]);d(this,"_selection");d(this,"_appliedSelection");d(this,"selectionCommitted",!1);d(this,"index",0);this.title=e.title,this._timezone=e.timezone,this.datasets=r,this.backend=i,this.features=s}get queries(){return Object.fromEntries(this._queries)}get controls(){return Object.fromEntries(this._controls)}get mappings(){return[...this._mappings,...this._crossFilters]}get crossFilters(){return this._crossFilters}createQuery(e,r){this.assertNameFree(e,"query");let i=new F(e,this,r);return this._queries.set(e,i),this.notify(),i}createFilter(e,r){this.assertNameFree(e,"filter");let i=r.dataset?this.datasetIndex(r.dataset,e):this.soleDataset(e,r.field),s=new J(e,this,r,i,this.backend);return this._controls.set(e,s),this.notify(),s}createDateDrill(e,r={}){if(this.features?.dateDrill===!1)throw new p(`Date drill \'${e}\' needs a capability this workspace has not enabled. Ask an admin to turn on \\`interactive_control:date_drill\\`.`,e);this.assertNameFree(e,"date drill control");let i=new j(e,this,r);return this._controls.set(e,i),this.notify(),i}mapControl(e,r,i){if(!(e instanceof I))throw new p("mapControl expects a control as its source. To let one query cross-filter another, use app.mapCrossFilter(from, to) \\u2014 a cross-filter edge names no field.");if(this._controls.get(e.name)!==e)throw new p(`Control \'${e.name}\' belongs to a different app.`,e.name);if(this._queries.get(r.name)!==r)throw new p(`Query \'${r.name}\' belongs to a different app.`,r.name);i.aggregation&&ue(i.aggregation,e.name);let s=new G(e,r,i.field,i.aggregation);if(this._mappings.some(l=>l.id===s.id))throw new p(`Mapping \'${s.id}\' is already declared.`,e.name);return this.assertAcyclic(s),this._mappings.push(s),this.notify(),s}mapCrossFilter(e,r){if(!(e instanceof F)||!(r instanceof F))throw new p("mapCrossFilter expects two queries. To let a control condition a query, use app.mapControl(from, to, { field }).");if(this._queries.get(e.name)!==e)throw new p(`Query \'${e.name}\' belongs to a different app.`,e.name);if(this._queries.get(r.name)!==r)throw new p(`Query \'${r.name}\' belongs to a different app.`,r.name);if(e===r)throw new p(`Query \'${e.name}\' cannot cross-filter itself. A source keeps its own rows so it can show what is selected; filtering it would erase the very rows the reader clicked.`,e.name);if(e.declaration.dataset!==r.declaration.dataset)throw new p(`Cross-filter \'${e.name}\' -> \'${r.name}\' spans two datasets (\'${e.declaration.dataset}\' and \'${r.declaration.dataset}\'). A selection carries field references, which mean nothing outside the dataset that resolved them.`,e.name);let i=new q(e,r);if(this._crossFilters.some(s=>s.id===i.id))throw new p(`Cross-filter \'${i.id}\' is already declared.`,e.name);return this._crossFilters.push(i),this.notify(),i}get selection(){return this._selection}get appliedSelection(){return this._appliedSelection}clearSelection(){this._selection&&(this._selection=void 0,this.notify())}setTimezone(e){this._timezone=e,this.notify()}get timezone(){return this._timezone}datasetIndex(e,r){return ce(this.datasets,e,r)}mappingsTo(e){return this._mappings.filter(r=>r.to.name===e)}selectionExpressionFor(e,r){return this.selectionFor(e,r)?.expression}selectionFor(e,r){let i=r==="pending"?this._selection:this._appliedSelection;return!i||i.source===e?void 0:this._crossFilters.some(l=>l.from.name===i.source&&l.to.name===e)?i:void 0}selectionConditionsFor(e,r){let i=r==="pending"?this._selection:this._appliedSelection;return!i||i.source===e?se:this._crossFilters.some(l=>l.from.name===i.source&&l.to.name===e)?i.conditions:se}drillTransformations(e){let r={};return this._mappings.forEach(i=>{if(i.to!==e)return;let s=i.from.appliedCondition;if(s?.operator!=="transform_date_drill")return;let[l]=s.values??[];typeof l=="string"&&(r[i.field]=l)}),r}select(e,r,i){if(this._queries.get(e.name)!==e)throw new p(`Query \'${e.name}\' belongs to a different app.`,e.name);let s=Se(e,r,i,this.drillTransformations(e));s?.lossy&&console.warn(`[anfra-sdk] The selection in \'${e.name}\' filters more broadly than it was made. Its conditions are combined field by field and ANDed, which cannot express "(A and B) or (C and D)", so the targets will also show combinations that were not selected. Select within one dimension, or narrow with \\`fields\\`, to avoid this.`),this._selection=s,this.notify()}get hasChanges(){return[...this._controls.values()].some(e=>e.isDirty)||this.isSelectionDirty}get isSelectionDirty(){return this.selectionCommitted?Ne(this._selection)!==Ne(this._appliedSelection):!1}async execute(e={}){let r=[...this._queries.values()].filter(o=>o.shouldRun(e.force));this._controls.forEach(o=>o.commit()),this._appliedSelection=this._selection,this.selectionCommitted=!0,r.forEach(o=>{o.controller?.abort(),o.resetPage()});let i={backend:this.backend,timezone:this._timezone,bustCache:e.bustCache},s=[],l=[];return await Promise.all(r.map(async o=>{let a=new AbortController;o.controller=a;let c=o.signature();o.markExecuting();try{let b=await ee(o,this.mappingsTo(o.name),{conditions:this.selectionConditionsFor(o.name,"applied"),expression:this.selectionExpressionFor(o.name,"applied")},i,a.signal);if($e(o,a))return;o.markSuccess(b,c,!1),s.push(o.name)}catch(b){if($e(o,a)||Re(b))return;o.markError(Te(b,o.name)),l.push(o.name)}finally{o.controller===a&&(o.controller=void 0)}})),{succeeded:s,failed:l}}async refresh(){return this.execute({force:!0,bustCache:!0})}abort(){this._queries.forEach(e=>e.abort())}async fetchPage(e){let r=new AbortController;e.controller=r;let i=e.signature();e.markExecuting();try{let s=await ee(e,this.mappingsTo(e.name),{conditions:this.selectionConditionsFor(e.name,"applied"),expression:this.selectionExpressionFor(e.name,"applied")},{backend:this.backend,timezone:this._timezone},r.signal);e.markSuccess(s,i,!0)}catch(s){if(e.rewindPage(),Re(s))return;let l=Te(s,e.name);throw e.markError(l),l}finally{e.controller===r&&(e.controller=void 0)}}toJSON(){let e=r=>Object.fromEntries([...this._controls.entries()].filter(([,i])=>i.kind===r).map(([i,s])=>[i,s.toJSON()]));return{...this.title?{title:this.title}:{},...this._timezone?{timezone:this._timezone}:{},queries:Object.fromEntries([...this._queries.entries()].map(([r,i])=>[r,i.toJSON()])),filters:e("filter"),dateDrills:e("dateDrill"),mappings:[...this._mappings.map(r=>r.toJSON()),...this._crossFilters.map(r=>r.toJSON())]}}toInspectJSON(){return{...this.title?{title:this.title}:{},...this._timezone?{timezone:this._timezone}:{},hasChanges:this.hasChanges,declaration:this.toJSON(),queries:Object.fromEntries([...this._queries.entries()].map(([e,r])=>[e,ht(r)])),controls:Object.fromEntries([...this._controls.entries()].map(([e,r])=>[e,gt(r)])),...this._selection?{selection:Oe(this._selection)}:{},...this._appliedSelection?{appliedSelection:Oe(this._appliedSelection)}:{}}}locate(e,r){let i=globalThis.document;if(!i)return!1;let s=Ce(i,e,this.index,r);return s.length?(H(s[0]),Q(i).flash(s,{kind:e,name:r,...s.length>1?{note:`in ${s.length} places`}:{}}),!0):!1}assertNameFree(e,r){pe([...this._queries.keys(),...this._controls.keys()],e,r)}soleDataset(e,r){if(!r)return;let i=Object.keys(this.datasets);if(i.length===1)return this.datasets[i[0]];throw new p(i.length?`Filter \'${e}\' names a field but the environment has ${i.length} datasets. Say which one: ${i.join(", ")}.`:`Filter \'${e}\' names a field but the environment has no datasets.`,e)}assertAcyclic(e){let r=[...this._mappings,e],i=new Map;r.forEach(a=>{let c=a.from.name;i.set(c,[...i.get(c)??[],a.to.name])});let s=new Set,l=new Set,o=a=>{if(s.has(a))return!0;if(l.has(a))return!1;s.add(a);let c=(i.get(a)??[]).some(o);return s.delete(a),l.add(a),c};if([...i.keys()].some(o))throw new p(`Mapping \'${e.id}\' would make the app\'s mappings cyclic.`,e.from.name)}};var Fe="0.7.0";var bt=Fe,oe=class{constructor(n){d(this,"version",bt);d(this,"_apps",[]);d(this,"datasets");d(this,"user");d(this,"indices");d(this,"backend");d(this,"features");if(!n?.datasets)throw new p("No datasets provided. The host must call createSdk({ datasets }) before an app can be declared.");if(!n.user)throw new p("No user provided. The host must call createSdk({ user }) before an app can be declared.");if(!n.backend)throw new p("No backend provided. The host must call createSdk({ backend }) before an app can run a query.");this.datasets=Object.freeze({...n.datasets}),this.user=Object.freeze({...n.user,permissions:Object.freeze({...n.user.permissions})}),this.indices=Object.fromEntries(Object.entries(n.datasets).map(([e,r])=>[e,new U(r)])),this.backend=n.backend,this.features=Object.freeze({...n.features})}createApp(n={}){let e=new W(n,this.indices,this.backend,this.features);return e.index=this._apps.length,this._apps.push(e),e}get apps(){return this._apps}};function Ie(t){return new oe(t)}var B="Anfra";function Me(t,n=globalThis){let e=n[B],r=B in n;return n[B]=t,()=>{r?n[B]=e:delete n[B]}}function Qe(t,n){let e=null,r=a=>a.target instanceof Element?a.target.closest(K):null,i=a=>{let c=r(a);c!==e&&(e=c,n.onHover(c))},s=a=>{a.preventDefault(),a.stopPropagation();let c=r(a);c&&n.onPick(c)},l=a=>{a.key==="Escape"&&n.onCancel()},o={capture:!0};return t.addEventListener("mousemove",i,o),t.addEventListener("click",s,o),t.addEventListener("keydown",l,o),()=>{t.removeEventListener("mousemove",i,o),t.removeEventListener("click",s,o),t.removeEventListener("keydown",l,o)}}function Le(){let t=new Error("Aborted");return t.name="AbortError",t}function wt(t){switch(t?.name){case"PermissionError":return new P(t.message);case"TransportError":return new V(t.message);default:return new T(t?.message||"The query failed.")}}function Be(t){let n=t.document.getElementById(ae);if(!n?.textContent)throw new Error("anfra: this frame was not provisioned by a host.");let e=JSON.parse(n.textContent),r=f=>t.parent.postMessage(f,e.hostOrigin),i=new Map,s=1,l=!1,o;t.addEventListener("message",f=>{if(f.source!==t.parent)return;let m=f.data;switch(m?.type){case"anfra:response":{let g=i.get(m.id);if(!g)return;i.delete(m.id),m.ok?g.resolve(m.result):g.reject(wt(m.error));return}case"anfra:inspect-watch":N(m.open);return;case"anfra:highlight":D(m.node);return;case"anfra:locate":k(m.target);return;case"anfra:pick-watch":R(m.on);return;default:}});function a(f,m,g){return new Promise((_,X)=>{if(g.aborted){X(Le());return}let z=s++;i.set(z,{resolve:_,reject:X}),g.addEventListener("abort",()=>{i.delete(z)&&(r({type:"anfra:cancel",id:z}),X(Le()))},{once:!0}),r({type:"anfra:request",id:z,method:f,request:m})})}let c={submitQuery:(f,m)=>a("submitQuery",f,m).then(g=>(g.debug?.executedAt&&(g.debug.executedAt=new Date(g.debug.executedAt)),g)),fieldSuggestions:(f,m)=>a("fieldSuggestions",f,m)},b=Ie({datasets:e.datasets,user:e.user,backend:c}),S=t.document,u,v="",x,E;function h(){if(o=void 0,!!l)try{u=re(S,b.apps),v=JSON.stringify(u.nodes),r({type:"anfra:inspect",apps:b.apps.map(f=>f.toInspectJSON()),structure:{nodes:u.nodes,usage:u.usage}})}catch{}}function y(){l&&o===void 0&&(o=setTimeout(h,50))}function A(){let f=t.MutationObserver;if(x||typeof f!="function")return;let m=new f(()=>{if(!(!l||o!==void 0))try{JSON.stringify(re(S,b.apps).nodes)!==v&&y()}catch{}});x=m,m.observe(S.documentElement,{childList:!0,subtree:!0,attributes:!0,attributeFilter:["data-anfra-container","data-anfra-block","data-anfra-query","data-anfra-control","data-anfra-label"]})}function N(f){l=f,f?(A(),h()):(x?.disconnect(),x=void 0,R(!1),Q(S).hide(),u=void 0,v="")}function w(f,m){for(let g of f){if(g.key===m)return g;let _=w(g.children,m);if(_)return _}}function D(f){let m=Q(S),g=f!==null&&u?w(u.nodes,f):void 0,_=f!==null?u?.elements.get(f):void 0;if(!g||!_||!u){m.hide();return}m.show([_],De(g,_,u))}function k(f){if("node"in f){let m=u?.elements.get(f.node);m&&H(m),D(f.node)}else b.apps[f.app]?.locate(f.kind,f.name);y()}function R(f){if(!f){E?.(),E=void 0;return}if(E)return;let m=g=>u?.keysOf.get(g)?.[0];E=Qe(S,{onHover:g=>D(g?m(g)??null:null),onPick:g=>{let _=m(g);_!==void 0&&r({type:"anfra:picked",node:_})},onCancel:()=>{R(!1),Q(S).hide(),r({type:"anfra:picked",node:null})}})}let Y=b.createApp.bind(b);b.createApp=f=>{let m=Y(f);return m.subscribe(y),y(),m},Me(b,t),r({type:"anfra:ready"})}Be(window);})();\n';
75
75
 
76
76
  // src/host/frameScript.ts
77
77
  function builtFrameScript() {
@@ -203,6 +203,32 @@ interface ColumnMeta {
203
203
  adhoc: boolean;
204
204
  isMeasure: boolean;
205
205
  aggregation?: string;
206
+ /**
207
+ * What the column's values mean: decide by it, never by `typeof`. A `number` column's values are
208
+ * strings with every digit (`"9007199254740993"`, `"12345.67"`, `"NaN"`). `unknown` when the
209
+ * query's type has no meaning in AML yet. See docs/designs/result-values.md.
210
+ */
211
+ semantic_type: SemanticType;
212
+ /** The Arrow type the values were read as: whether a JS number would keep every digit, say. */
213
+ arrow_type: ArrowType;
214
+ /** The database's own name of the type: for debugging, never to decide on. */
215
+ db_type: string;
216
+ }
217
+ /**
218
+ * What a column's values mean, in AML's types, plus `bit`: a true or false stored as a bit, written
219
+ * as `true` and `false` or as `"1"` and `"0"` by how the database produced it. `unknown` for a type
220
+ * AML gives no meaning yet.
221
+ */
222
+ type SemanticType = 'text' | 'number' | 'date' | 'datetime' | 'truefalse' | 'json' | 'bit' | 'unknown';
223
+ /**
224
+ * An Arrow type: Arrow's `Type` of format/Schema.fbs as JSON. `type` is its name (`int`,
225
+ * `floating_point`, `decimal`, `utf8`, `timestamp`, …), its other keys that type's fields in
226
+ * snake_case (`bit_width`, `scale`, `unit`, …), and `label` how arrow-go spells it, for people.
227
+ */
228
+ interface ArrowType {
229
+ type: string;
230
+ label: string;
231
+ [field: string]: unknown;
206
232
  }
207
233
  type Row = Record<string, unknown>;
208
234
  /**
@@ -516,4 +542,4 @@ interface Backend {
516
542
  fieldSuggestions(request: FieldSuggestionsRequest, signal: AbortSignal): Promise<ConditionValue[]>;
517
543
  }
518
544
 
519
- export { type Unsubscribe as $, type Aggregation as A, type Backend as B, type ColumnMeta as C, type DatasetDescriptor as D, type EntityKind as E, FILTER_AGGREGATIONS as F, type InspectedSelection as G, type InspectedStructure as H, type InspectedApp as I, type LocateKind as J, type LocateTarget as K, type Listener as L, type MappingJson as M, type QueryDeclaration as N, type Operator as O, type QueryInput as P, type QueryDebug as Q, type QueryResult as R, type QuerySort as S, type QueryState as T, type Row as U, type SdkEnvironment as V, type SdkFeatures as W, type Selection as X, type SelectionCondition as Y, type StructureKind as Z, type StructureNode as _, type AppDeclaration as a, type User as a0, type AppJson as b, type BackendCondition as c, type BackendDateDrill as d, type BackendFilter as e, type BackendQueryRequest as f, type BackendQueryResult as g, type BackendSort as h, type Condition as i, type ConditionValue as j, type ControlMappingJson as k, type CrossFilterJson as l, type DateDrillDeclaration as m, type DateGrain as n, type DateTransformation as o, type EntityRef as p, type EntityUsage as q, type ExecuteOptions as r, type ExecuteSummary as s, type FieldSuggestionsRequest as t, type FilterAggregation as u, type FilterDeclaration as v, type FilterValueType as w, type InspectedControl as x, type InspectedError as y, type InspectedQuery as z };
545
+ export { type StructureKind as $, type Aggregation as A, type Backend as B, type ColumnMeta as C, type DatasetDescriptor as D, type EntityKind as E, FILTER_AGGREGATIONS as F, type InspectedQuery as G, type InspectedSelection as H, type InspectedApp as I, type InspectedStructure as J, type LocateKind as K, type Listener as L, type LocateTarget as M, type MappingJson as N, type Operator as O, type QueryDeclaration as P, type QueryDebug as Q, type QueryInput as R, type QueryResult as S, type QuerySort as T, type QueryState as U, type Row as V, type SdkEnvironment as W, type SdkFeatures as X, type Selection as Y, type SelectionCondition as Z, type SemanticType as _, type AppDeclaration as a, type StructureNode as a0, type Unsubscribe as a1, type User as a2, type AppJson as b, type ArrowType as c, type BackendCondition as d, type BackendDateDrill as e, type BackendFilter as f, type BackendQueryRequest as g, type BackendQueryResult as h, type BackendSort as i, type Condition as j, type ConditionValue as k, type ControlMappingJson as l, type CrossFilterJson as m, type DateDrillDeclaration as n, type DateGrain as o, type DateTransformation as p, type EntityRef as q, type EntityUsage as r, type ExecuteOptions as s, type ExecuteSummary as t, type FieldSuggestionsRequest as u, type FilterAggregation as v, type FilterDeclaration as w, type FilterValueType as x, type InspectedControl as y, type InspectedError as z };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@holistics/anfra-sdk",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Data Apps on anfra: the authoring layer (app), what hosts one (host), the core API client (api), and their shared contract (common)",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {