@uwdata/mosaic-inputs 0.8.0 → 0.9.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.
@@ -4914,10 +4914,10 @@ var getDictionary = (data, index) => {
4914
4914
  var getInterval = (data, index) => data.type.unit === IntervalUnit.DAY_TIME ? getIntervalDayTime(data, index) : getIntervalYearMonth(data, index);
4915
4915
  var getIntervalDayTime = ({ values }, index) => values.subarray(2 * index, 2 * (index + 1));
4916
4916
  var getIntervalYearMonth = ({ values }, index) => {
4917
- const interval = values[index];
4917
+ const interval2 = values[index];
4918
4918
  const int32s = new Int32Array(2);
4919
- int32s[0] = Math.trunc(interval / 12);
4920
- int32s[1] = Math.trunc(interval % 12);
4919
+ int32s[0] = Math.trunc(interval2 / 12);
4920
+ int32s[1] = Math.trunc(interval2 % 12);
4921
4921
  return int32s;
4922
4922
  };
4923
4923
  var getDurationSecond = ({ values }, index) => values[index];
@@ -11839,6 +11839,13 @@ function socketConnector(uri = "ws://localhost:3000/") {
11839
11839
  get connected() {
11840
11840
  return connected;
11841
11841
  },
11842
+ /**
11843
+ * Query the DuckDB server.
11844
+ * @param {object} query
11845
+ * @param {'exec' | 'arrow' | 'json'} [query.type] The query type: 'exec', 'arrow', or 'json'.
11846
+ * @param {string} query.sql A SQL query string.
11847
+ * @returns the query result
11848
+ */
11842
11849
  query(query) {
11843
11850
  return new Promise(
11844
11851
  (resolve, reject) => enqueue(query, resolve, reject)
@@ -11909,7 +11916,7 @@ function literalToSQL(value) {
11909
11916
  case "boolean":
11910
11917
  return value ? "TRUE" : "FALSE";
11911
11918
  case "string":
11912
- return `'${value}'`;
11919
+ return `'${value.replace(`'`, `''`)}'`;
11913
11920
  case "number":
11914
11921
  return Number.isFinite(value) ? String(value) : "NULL";
11915
11922
  default:
@@ -11992,7 +11999,7 @@ var SQLExpression = class {
11992
11999
  /**
11993
12000
  * Annotate this expression instance with additional properties.
11994
12001
  * @param {object[]} [props] One or more objects with properties to add.
11995
- * @returns {this} This SQL expression.
12002
+ * @returns This SQL expression.
11996
12003
  */
11997
12004
  annotate(...props) {
11998
12005
  return Object.assign(this, ...props);
@@ -12260,6 +12267,9 @@ var last_value = winf("LAST_VALUE");
12260
12267
  var nth_value = winf("NTH_VALUE");
12261
12268
 
12262
12269
  // ../sql/src/aggregates.js
12270
+ function agg(strings, ...exprs) {
12271
+ return sql(strings, ...exprs).annotate({ aggregate: true });
12272
+ }
12263
12273
  var AggregateFunction = class _AggregateFunction extends SQLExpression {
12264
12274
  /**
12265
12275
  * Create a new AggregateFunction instance.
@@ -12390,6 +12400,7 @@ var entropy = aggf("ENTROPY");
12390
12400
  var varPop = aggf("VAR_POP");
12391
12401
  var stddevPop = aggf("STDDEV_POP");
12392
12402
  var corr = aggf("CORR");
12403
+ var covariance = aggf("COVAR_SAMP");
12393
12404
  var covarPop = aggf("COVAR_POP");
12394
12405
  var regrIntercept = aggf("REGR_INTERCEPT");
12395
12406
  var regrSlope = aggf("REGR_SLOPE");
@@ -12532,7 +12543,8 @@ var Query = class _Query {
12532
12543
  }
12533
12544
  }
12534
12545
  }
12535
- query.select = query.select.concat(list);
12546
+ const keys = new Set(list.map((x2) => x2.as));
12547
+ query.select = query.select.filter((x2) => !keys.has(x2.as)).concat(list.filter((x2) => x2.expr));
12536
12548
  return this;
12537
12549
  }
12538
12550
  }
@@ -13005,6 +13017,7 @@ function scaleTime() {
13005
13017
  };
13006
13018
  }
13007
13019
  var scales = {
13020
+ identity: scaleLinear,
13008
13021
  linear: scaleLinear,
13009
13022
  log: scaleLog,
13010
13023
  symlog: scaleSymlog,
@@ -13050,6 +13063,247 @@ function fnv_mix(a) {
13050
13063
  return a & 4294967295;
13051
13064
  }
13052
13065
 
13066
+ // ../core/src/util/index-columns.js
13067
+ var NO_INDEX = { from: NaN };
13068
+ function indexColumns(client) {
13069
+ if (!client.filterIndexable) return NO_INDEX;
13070
+ const q = client.query();
13071
+ const from = getBaseTable(q);
13072
+ if (typeof from !== "string" || !q.groupby) return NO_INDEX;
13073
+ const g = new Set(q.groupby().map((c) => c.column));
13074
+ const aggr = [];
13075
+ const dims = [];
13076
+ const aux = {};
13077
+ for (const entry of q.select()) {
13078
+ const { as, expr: { aggregate, args } } = entry;
13079
+ const op = aggregate?.toUpperCase?.();
13080
+ switch (op) {
13081
+ case "COUNT":
13082
+ case "SUM":
13083
+ aggr.push({ [as]: agg`SUM("${as}")::DOUBLE` });
13084
+ break;
13085
+ case "AVG":
13086
+ aggr.push({ [as]: avgExpr(aux, as, args[0]) });
13087
+ break;
13088
+ case "ARG_MAX":
13089
+ aggr.push({ [as]: argmaxExpr(aux, as, args) });
13090
+ break;
13091
+ case "ARG_MIN":
13092
+ aggr.push({ [as]: argminExpr(aux, as, args) });
13093
+ break;
13094
+ case "VARIANCE":
13095
+ case "VAR_SAMP":
13096
+ aux[as] = null;
13097
+ aggr.push({ [as]: varianceExpr(aux, args[0], from) });
13098
+ break;
13099
+ case "VAR_POP":
13100
+ aux[as] = null;
13101
+ aggr.push({ [as]: varianceExpr(aux, args[0], from, false) });
13102
+ break;
13103
+ case "STDDEV":
13104
+ case "STDDEV_SAMP":
13105
+ aux[as] = null;
13106
+ aggr.push({ [as]: agg`SQRT(${varianceExpr(aux, args[0], from)})` });
13107
+ break;
13108
+ case "STDDEV_POP":
13109
+ aux[as] = null;
13110
+ aggr.push({ [as]: agg`SQRT(${varianceExpr(aux, args[0], from, false)})` });
13111
+ break;
13112
+ case "COVAR_SAMP":
13113
+ aux[as] = null;
13114
+ aggr.push({ [as]: covarianceExpr(aux, args, from) });
13115
+ break;
13116
+ case "COVAR_POP":
13117
+ aux[as] = null;
13118
+ aggr.push({ [as]: covarianceExpr(aux, args, from, false) });
13119
+ break;
13120
+ case "CORR":
13121
+ aux[as] = null;
13122
+ aggr.push({ [as]: corrExpr(aux, args, from) });
13123
+ break;
13124
+ case "REGR_COUNT":
13125
+ aux[as] = null;
13126
+ aggr.push({ [as]: agg`${regrCountExpr(aux, args)}::DOUBLE` });
13127
+ break;
13128
+ case "REGR_AVGX":
13129
+ aux[as] = null;
13130
+ aggr.push({ [as]: regrAvgXExpr(aux, args) });
13131
+ break;
13132
+ case "REGR_AVGY":
13133
+ aux[as] = null;
13134
+ aggr.push({ [as]: regrAvgYExpr(aux, args) });
13135
+ break;
13136
+ case "REGR_SYY":
13137
+ aux[as] = null;
13138
+ aggr.push({ [as]: regrVarExpr(aux, 0, args, from) });
13139
+ break;
13140
+ case "REGR_SXX":
13141
+ aux[as] = null;
13142
+ aggr.push({ [as]: regrVarExpr(aux, 1, args, from) });
13143
+ break;
13144
+ case "REGR_SXY":
13145
+ aux[as] = null;
13146
+ aggr.push({ [as]: covarianceExpr(aux, args, from, null) });
13147
+ break;
13148
+ case "REGR_SLOPE":
13149
+ aux[as] = null;
13150
+ aggr.push({ [as]: regrSlopeExpr(aux, args, from) });
13151
+ break;
13152
+ case "REGR_INTERCEPT":
13153
+ aux[as] = null;
13154
+ aggr.push({ [as]: regrInterceptExpr(aux, args, from) });
13155
+ break;
13156
+ case "REGR_R2":
13157
+ aux[as] = null;
13158
+ aggr.push({ [as]: agg`(${corrExpr(aux, args, from)}) ** 2` });
13159
+ break;
13160
+ case "MAX":
13161
+ case "MIN":
13162
+ case "BIT_AND":
13163
+ case "BIT_OR":
13164
+ case "BIT_XOR":
13165
+ case "BOOL_AND":
13166
+ case "BOOL_OR":
13167
+ case "PRODUCT":
13168
+ aggr.push({ [as]: agg`${op}("${as}")` });
13169
+ break;
13170
+ default:
13171
+ if (g.has(as)) dims.push(as);
13172
+ else return null;
13173
+ }
13174
+ }
13175
+ return { from, dims, aggr, aux };
13176
+ }
13177
+ function auxName(type, ...args) {
13178
+ const cols = args.length ? "_" + args.map(sanitize).join("_") : "";
13179
+ return `__${type}${cols}__`;
13180
+ }
13181
+ function sanitize(col) {
13182
+ return `${col}`.replaceAll('"', "").replaceAll(" ", "_");
13183
+ }
13184
+ function getBaseTable(query) {
13185
+ const subq = query.subqueries;
13186
+ if (query.select) {
13187
+ const from = query.from();
13188
+ if (!from.length) return void 0;
13189
+ if (subq.length === 0) return from[0].from.table;
13190
+ }
13191
+ const base = getBaseTable(subq[0]);
13192
+ for (let i = 1; i < subq.length; ++i) {
13193
+ const from = getBaseTable(subq[i]);
13194
+ if (from === void 0) continue;
13195
+ if (from !== base) return NaN;
13196
+ }
13197
+ return base;
13198
+ }
13199
+ function countExpr(aux, arg) {
13200
+ const n = auxName("count", arg);
13201
+ aux[n] = agg`COUNT(${arg})`;
13202
+ return agg`SUM(${n})`.annotate({ name: n });
13203
+ }
13204
+ function avgExpr(aux, as, arg) {
13205
+ const n = countExpr(aux, arg);
13206
+ return agg`(SUM("${as}" * ${n.name}) / ${n})`;
13207
+ }
13208
+ function avg2(x2, from) {
13209
+ return sql`(SELECT AVG(${x2}) FROM "${from}")`;
13210
+ }
13211
+ function argmaxExpr(aux, as, [, y2]) {
13212
+ const max2 = auxName("max", y2);
13213
+ aux[max2] = agg`MAX(${y2})`;
13214
+ return agg`ARG_MAX("${as}", ${max2})`;
13215
+ }
13216
+ function argminExpr(aux, as, [, y2]) {
13217
+ const min2 = auxName("min", y2);
13218
+ aux[min2] = agg`MIN(${y2})`;
13219
+ return agg`ARG_MIN("${as}", ${min2})`;
13220
+ }
13221
+ function varianceExpr(aux, x2, from, correction = true) {
13222
+ const n = countExpr(aux, x2);
13223
+ const ssq = auxName("rssq", x2);
13224
+ const sum2 = auxName("rsum", x2);
13225
+ const delta = sql`${x2} - ${avg2(x2, from)}`;
13226
+ aux[ssq] = agg`SUM((${delta}) ** 2)`;
13227
+ aux[sum2] = agg`SUM(${delta})`;
13228
+ const adj = correction ? ` - 1` : "";
13229
+ return agg`(SUM(${ssq}) - (SUM(${sum2}) ** 2 / ${n})) / (${n}${adj})`;
13230
+ }
13231
+ function covarianceExpr(aux, args, from, correction = true) {
13232
+ const n = regrCountExpr(aux, args);
13233
+ const sxy = regrSumXYExpr(aux, args, from);
13234
+ const sx = regrSumExpr(aux, 1, args, from);
13235
+ const sy = regrSumExpr(aux, 0, args, from);
13236
+ const adj = correction === null ? "" : correction ? ` / (${n} - 1)` : ` / ${n}`;
13237
+ return agg`(${sxy} - ${sx} * ${sy} / ${n})${adj}`;
13238
+ }
13239
+ function corrExpr(aux, args, from) {
13240
+ const n = regrCountExpr(aux, args);
13241
+ const sxy = regrSumXYExpr(aux, args, from);
13242
+ const sxx = regrSumSqExpr(aux, 1, args, from);
13243
+ const syy = regrSumSqExpr(aux, 0, args, from);
13244
+ const sx = regrSumExpr(aux, 1, args, from);
13245
+ const sy = regrSumExpr(aux, 0, args, from);
13246
+ const vx = agg`(${sxx} - (${sx} ** 2) / ${n})`;
13247
+ const vy = agg`(${syy} - (${sy} ** 2) / ${n})`;
13248
+ return agg`(${sxy} - ${sx} * ${sy} / ${n}) / SQRT(${vx} * ${vy})`;
13249
+ }
13250
+ function regrCountExpr(aux, [y2, x2]) {
13251
+ const n = auxName("count", y2, x2);
13252
+ aux[n] = agg`REGR_COUNT(${y2}, ${x2})`;
13253
+ return agg`SUM(${n})`.annotate({ name: n });
13254
+ }
13255
+ function regrSumExpr(aux, i, args, from) {
13256
+ const v = args[i];
13257
+ const o = args[1 - i];
13258
+ const sum2 = auxName("rs", v);
13259
+ aux[sum2] = agg`SUM(${v} - ${avg2(v, from)}) FILTER (${o} IS NOT NULL)`;
13260
+ return agg`SUM(${sum2})`;
13261
+ }
13262
+ function regrSumSqExpr(aux, i, args, from) {
13263
+ const v = args[i];
13264
+ const u = args[1 - i];
13265
+ const ssq = auxName("rss", v);
13266
+ aux[ssq] = agg`SUM((${v} - ${avg2(v, from)}) ** 2) FILTER (${u} IS NOT NULL)`;
13267
+ return agg`SUM(${ssq})`;
13268
+ }
13269
+ function regrSumXYExpr(aux, args, from) {
13270
+ const [y2, x2] = args;
13271
+ const sxy = auxName("sxy", y2, x2);
13272
+ aux[sxy] = agg`SUM((${x2} - ${avg2(x2, from)}) * (${y2} - ${avg2(y2, from)}))`;
13273
+ return agg`SUM(${sxy})`;
13274
+ }
13275
+ function regrAvgXExpr(aux, args) {
13276
+ const [y2, x2] = args;
13277
+ const n = regrCountExpr(aux, args);
13278
+ const a = auxName("avg", x2, y2);
13279
+ aux[a] = agg`REGR_AVGX(${y2}, ${x2})`;
13280
+ return agg`(SUM(${a} * ${n.name}) / ${n})`;
13281
+ }
13282
+ function regrAvgYExpr(aux, args) {
13283
+ const [y2, x2] = args;
13284
+ const n = regrCountExpr(aux, args);
13285
+ const a = auxName("avg", y2, x2);
13286
+ aux[a] = agg`REGR_AVGY(${y2}, ${x2})`;
13287
+ return agg`(SUM(${a} * ${n.name}) / ${n})`;
13288
+ }
13289
+ function regrVarExpr(aux, i, args, from) {
13290
+ const n = regrCountExpr(aux, args);
13291
+ const sum2 = regrSumExpr(aux, i, args, from);
13292
+ const ssq = regrSumSqExpr(aux, i, args, from);
13293
+ return agg`(${ssq} - (${sum2} ** 2 / ${n}))`;
13294
+ }
13295
+ function regrSlopeExpr(aux, args, from) {
13296
+ const cov = covarianceExpr(aux, args, from, null);
13297
+ const varx = regrVarExpr(aux, 1, args, from);
13298
+ return agg`(${cov}) / ${varx}`;
13299
+ }
13300
+ function regrInterceptExpr(aux, args, from) {
13301
+ const ax = regrAvgXExpr(aux, args);
13302
+ const ay = regrAvgYExpr(aux, args);
13303
+ const m = regrSlopeExpr(aux, args, from);
13304
+ return agg`${ay} - (${m}) * ${ax}`;
13305
+ }
13306
+
13053
13307
  // ../core/src/DataCubeIndexer.js
13054
13308
  var DataCubeIndexer = class {
13055
13309
  /**
@@ -13067,42 +13321,49 @@ var DataCubeIndexer = class {
13067
13321
  this.enabled = false;
13068
13322
  this.clients = null;
13069
13323
  this.indices = null;
13070
- this.activeView = null;
13324
+ this.active = null;
13071
13325
  }
13072
13326
  clear() {
13073
13327
  if (this.indices) {
13074
- this.mc.cancel(Array.from(this.indices.values(), (index) => index.result));
13328
+ this.mc.cancel(Array.from(this.indices.values(), (index) => index?.result));
13075
13329
  this.indices = null;
13076
13330
  }
13077
13331
  }
13078
- index(clients, active) {
13332
+ index(clients, activeClause) {
13079
13333
  if (this.clients !== clients) {
13080
- const cols = Array.from(clients, getIndexColumns);
13334
+ const cols = Array.from(clients, indexColumns).filter((x2) => x2);
13081
13335
  const from = cols[0]?.from;
13082
- this.enabled = cols.every((c) => c && c.from === from);
13336
+ this.enabled = cols.length && cols.every((c) => c.from === from);
13083
13337
  this.clients = clients;
13084
- this.activeView = null;
13338
+ this.active = null;
13085
13339
  this.clear();
13086
13340
  }
13087
13341
  if (!this.enabled) return false;
13088
- active = active || this.selection.active;
13089
- const { source } = active;
13090
- if (source && source === this.activeView?.source) return true;
13342
+ activeClause = activeClause || this.selection.active;
13343
+ const { source } = activeClause;
13344
+ if (source && source === this.active?.source) return true;
13091
13345
  this.clear();
13092
13346
  if (!source) return false;
13093
- const activeView = this.activeView = getActiveView(active);
13094
- if (!activeView) return false;
13095
- this.mc.logger().warn("DATA CUBE INDEX CONSTRUCTION");
13347
+ const active = this.active = activeColumns(activeClause);
13348
+ if (!active) return false;
13349
+ const logger = this.mc.logger();
13350
+ logger.warn("DATA CUBE INDEX CONSTRUCTION");
13096
13351
  const sel = this.selection.remove(source);
13097
13352
  const indices = this.indices = /* @__PURE__ */ new Map();
13098
13353
  const { mc, temp } = this;
13099
13354
  for (const client of clients) {
13100
- if (sel.skip(client, active)) continue;
13101
- const index = getIndexColumns(client);
13102
- const query = client.query(sel.predicate(client)).select({ ...activeView.columns, ...index.aux }).groupby(Object.keys(activeView.columns));
13355
+ if (sel.skip(client, activeClause)) {
13356
+ indices.set(client, null);
13357
+ continue;
13358
+ }
13359
+ const index = indexColumns(client);
13360
+ if (!index) {
13361
+ continue;
13362
+ }
13363
+ const query = client.query(sel.predicate(client)).select({ ...active.columns, ...index.aux }).groupby(Object.keys(active.columns));
13103
13364
  const [subq] = query.subqueries;
13104
13365
  if (subq) {
13105
- const cols = Object.values(activeView.columns).map((c) => c.columns[0]);
13366
+ const cols = Object.values(active.columns).flatMap((c) => c.columns);
13106
13367
  subqueryPushdown(subq, cols);
13107
13368
  }
13108
13369
  const order = query.orderby();
@@ -13111,36 +13372,40 @@ var DataCubeIndexer = class {
13111
13372
  const id = (fnv_hash(sql2) >>> 0).toString(16);
13112
13373
  const table2 = `cube_index_${id}`;
13113
13374
  const result = mc.exec(create(table2, sql2, { temp }));
13375
+ result.catch((e) => logger.error(e));
13114
13376
  indices.set(client, { table: table2, result, order, ...index });
13115
13377
  }
13116
13378
  return true;
13117
13379
  }
13118
13380
  async update() {
13119
- const { clients, selection, activeView } = this;
13120
- const filter = activeView.predicate(selection.active.predicate);
13381
+ const { clients, selection, active } = this;
13382
+ const filter = active.predicate(selection.active.predicate);
13121
13383
  return Promise.all(
13122
13384
  Array.from(clients).map((client) => this.updateClient(client, filter))
13123
13385
  );
13124
13386
  }
13125
13387
  async updateClient(client, filter) {
13388
+ const { mc, indices, selection } = this;
13389
+ if (!indices.has(client)) {
13390
+ filter = selection.predicate(client);
13391
+ return mc.updateClient(client, client.query(filter));
13392
+ }
13393
+ ;
13126
13394
  const index = this.indices.get(client);
13127
13395
  if (!index) return;
13128
- if (!filter) {
13129
- filter = this.activeView.predicate(this.selection.active.predicate);
13130
- }
13131
13396
  const { table: table2, dims, aggr, order = [] } = index;
13132
13397
  const query = Query.select(dims, aggr).from(table2).groupby(dims).where(filter).orderby(order);
13133
- return this.mc.updateClient(client, query);
13398
+ return mc.updateClient(client, query);
13134
13399
  }
13135
13400
  };
13136
- function getActiveView(clause) {
13137
- const { source, schema } = clause;
13401
+ function activeColumns(clause) {
13402
+ const { source, meta } = clause;
13138
13403
  let columns = clause.predicate?.columns;
13139
- if (!schema || !columns) return null;
13140
- const { type, scales: scales2, pixelSize = 1 } = schema;
13404
+ if (!meta || !columns) return null;
13405
+ const { type, scales: scales2, bin, pixelSize = 1 } = meta;
13141
13406
  let predicate;
13142
13407
  if (type === "interval" && scales2) {
13143
- const bins = scales2.map((s) => binInterval(s, pixelSize));
13408
+ const bins = scales2.map((s) => binInterval(s, pixelSize, bin));
13144
13409
  if (bins.some((b) => b == null)) return null;
13145
13410
  if (bins.length === 1) {
13146
13411
  predicate = (p) => p ? isBetween("active0", p.range.map(bins[0])) : [];
@@ -13153,85 +13418,23 @@ function getActiveView(clause) {
13153
13418
  }
13154
13419
  } else if (type === "point") {
13155
13420
  predicate = (x2) => x2;
13156
- columns = Object.fromEntries(columns.map((col) => [col.toString(), col]));
13421
+ columns = Object.fromEntries(columns.map((col) => [`${col}`, asColumn(col)]));
13157
13422
  } else {
13158
13423
  return null;
13159
13424
  }
13160
13425
  return { source, columns, predicate };
13161
13426
  }
13162
- function binInterval(scale, pixelSize) {
13163
- const { apply, sqlApply } = scaleTransform(scale);
13164
- if (apply) {
13165
- const { domain, range } = scale;
13166
- const lo = apply(Math.min(...domain));
13167
- const hi = apply(Math.max(...domain));
13168
- const a = Math.abs(range[1] - range[0]) / (hi - lo) / pixelSize;
13169
- const s = pixelSize === 1 ? "" : `${pixelSize}::INTEGER * `;
13170
- return (value) => sql`${s}FLOOR(${a}::DOUBLE * (${sqlApply(value)} - ${lo}::DOUBLE))::INTEGER`;
13171
- }
13172
- }
13173
- var NO_INDEX = { from: NaN };
13174
- function getIndexColumns(client) {
13175
- if (!client.filterIndexable) return NO_INDEX;
13176
- const q = client.query();
13177
- const from = getBaseTable(q);
13178
- if (!from || !q.groupby) return NO_INDEX;
13179
- const g = new Set(q.groupby().map((c) => c.column));
13180
- const aggr = [];
13181
- const dims = [];
13182
- const aux = {};
13183
- let auxAs;
13184
- for (const entry of q.select()) {
13185
- const { as, expr: { aggregate, args } } = entry;
13186
- const op = aggregate?.toUpperCase?.();
13187
- switch (op) {
13188
- case "COUNT":
13189
- case "SUM":
13190
- aggr.push({ [as]: sql`SUM("${as}")::DOUBLE` });
13191
- break;
13192
- case "AVG":
13193
- aux[auxAs = "__count__"] = sql`COUNT(*)`;
13194
- aggr.push({ [as]: sql`(SUM("${as}" * ${auxAs}) / SUM(${auxAs}))::DOUBLE` });
13195
- break;
13196
- case "ARG_MAX":
13197
- aux[auxAs = `__max_${as}__`] = sql`MAX(${args[1]})`;
13198
- aggr.push({ [as]: sql`ARG_MAX("${as}", ${auxAs})` });
13199
- break;
13200
- case "ARG_MIN":
13201
- aux[auxAs = `__min_${as}__`] = sql`MIN(${args[1]})`;
13202
- aggr.push({ [as]: sql`ARG_MIN("${as}", ${auxAs})` });
13203
- break;
13204
- case "MAX":
13205
- case "MIN":
13206
- case "BIT_AND":
13207
- case "BIT_OR":
13208
- case "BIT_XOR":
13209
- case "BOOL_AND":
13210
- case "BOOL_OR":
13211
- case "PRODUCT":
13212
- aggr.push({ [as]: sql`${op}("${as}")` });
13213
- break;
13214
- default:
13215
- if (g.has(as)) dims.push(as);
13216
- else return null;
13217
- }
13218
- }
13219
- return { aggr, dims, aux, from };
13220
- }
13221
- function getBaseTable(query) {
13222
- const subq = query.subqueries;
13223
- if (query.select) {
13224
- const from = query.from();
13225
- if (!from.length) return void 0;
13226
- if (subq.length === 0) return from[0].from.table;
13227
- }
13228
- const base = getBaseTable(subq[0]);
13229
- for (let i = 1; i < subq.length; ++i) {
13230
- const from = getBaseTable(subq[i]);
13231
- if (from === void 0) continue;
13232
- if (from !== base) return NaN;
13233
- }
13234
- return base;
13427
+ var BIN = { ceil: "CEIL", round: "ROUND" };
13428
+ function binInterval(scale, pixelSize, bin) {
13429
+ const { type, domain, range, apply, sqlApply } = scaleTransform(scale);
13430
+ if (!apply) return;
13431
+ const fn = BIN[`${bin}`.toLowerCase()] || "FLOOR";
13432
+ const lo = apply(Math.min(...domain));
13433
+ const hi = apply(Math.max(...domain));
13434
+ const a = type === "identity" ? 1 : Math.abs(range[1] - range[0]) / (hi - lo);
13435
+ const s = a / pixelSize === 1 ? "" : `${a / pixelSize}::DOUBLE * `;
13436
+ const d = lo === 0 ? "" : ` - ${lo}::DOUBLE`;
13437
+ return (value) => sql`${fn}(${s}(${sqlApply(value)}${d}))::INTEGER`;
13235
13438
  }
13236
13439
  function subqueryPushdown(query, cols) {
13237
13440
  const memo = /* @__PURE__ */ new Set();
@@ -13246,1258 +13449,1321 @@ function subqueryPushdown(query, cols) {
13246
13449
  pushdown(query);
13247
13450
  }
13248
13451
 
13249
- // ../core/src/FilterGroup.js
13250
- var FilterGroup = class {
13452
+ // ../core/src/util/AsyncDispatch.js
13453
+ var AsyncDispatch = class {
13251
13454
  /**
13252
- * @param {import('./Coordinator.js').Coordinator} coordinator The Mosaic coordinator.
13253
- * @param {*} selection The shared filter selection.
13254
- * @param {*} index Boolean flag or options hash for data cube indexer.
13255
- * Falsy values disable indexing.
13455
+ * Create a new asynchronous dispatcher instance.
13256
13456
  */
13257
- constructor(coordinator2, selection, index = true) {
13258
- this.mc = coordinator2;
13259
- this.selection = selection;
13260
- this.clients = /* @__PURE__ */ new Set();
13261
- this.indexer = index ? new DataCubeIndexer(this.mc, { ...index, selection }) : null;
13262
- const { value, activate } = this.handlers = {
13263
- value: () => this.update(),
13264
- activate: (clause) => this.indexer?.index(this.clients, clause)
13265
- };
13266
- selection.addEventListener("value", value);
13267
- selection.addEventListener("activate", activate);
13457
+ constructor() {
13458
+ this._callbacks = /* @__PURE__ */ new Map();
13268
13459
  }
13269
- finalize() {
13270
- const { value, activate } = this.handlers;
13271
- this.selection.removeEventListener("value", value);
13272
- this.selection.removeEventListener("activate", activate);
13460
+ /**
13461
+ * Add an event listener callback for the provided event type.
13462
+ * @param {string} type The event type.
13463
+ * @param {(value: *) => void | Promise} callback The event handler
13464
+ * callback function to add. If the callback has already been
13465
+ * added for the event type, this method has no effect.
13466
+ */
13467
+ addEventListener(type, callback) {
13468
+ if (!this._callbacks.has(type)) {
13469
+ this._callbacks.set(type, {
13470
+ callbacks: /* @__PURE__ */ new Set(),
13471
+ pending: null,
13472
+ queue: new DispatchQueue()
13473
+ });
13474
+ }
13475
+ const entry = this._callbacks.get(type);
13476
+ entry.callbacks.add(callback);
13273
13477
  }
13274
- reset() {
13275
- this.indexer?.reset();
13478
+ /**
13479
+ * Remove an event listener callback for the provided event type.
13480
+ * @param {string} type The event type.
13481
+ * @param {(value: *) => void | Promise} callback The event handler
13482
+ * callback function to remove.
13483
+ */
13484
+ removeEventListener(type, callback) {
13485
+ const entry = this._callbacks.get(type);
13486
+ if (entry) {
13487
+ entry.callbacks.delete(callback);
13488
+ }
13276
13489
  }
13277
- add(client) {
13278
- (this.clients = new Set(this.clients)).add(client);
13279
- return this;
13490
+ /**
13491
+ * Lifecycle method that returns the event value to emit.
13492
+ * This default implementation simply returns the input value as-is.
13493
+ * Subclasses may override this method to implement custom transformations
13494
+ * prior to emitting an event value to all listeners.
13495
+ * @param {string} type The event type.
13496
+ * @param {*} value The event value.
13497
+ * @returns The (possibly transformed) event value to emit.
13498
+ */
13499
+ willEmit(type, value) {
13500
+ return value;
13280
13501
  }
13281
- remove(client) {
13282
- if (this.clients.has(client)) {
13283
- (this.clients = new Set(this.clients)).delete(client);
13284
- }
13285
- return this;
13502
+ /**
13503
+ * Lifecycle method that returns a filter function for updating the
13504
+ * queue of unemitted event values prior to enqueueing a new value.
13505
+ * This default implementation simply returns null, indicating that
13506
+ * any other unemitted event values should be dropped (that is, all
13507
+ * queued events are filtered)
13508
+ * @param {string} type The event type.
13509
+ * @param {*} value The new event value that will be enqueued.
13510
+ * @returns {(value: *) => boolean|null} A dispatch queue filter
13511
+ * function, or null if all unemitted event values should be filtered.
13512
+ */
13513
+ emitQueueFilter(type, value) {
13514
+ return null;
13286
13515
  }
13287
13516
  /**
13288
- * Internal method to process a selection update.
13289
- * The return value is passed as a selection callback value.
13290
- * @returns {Promise} A Promise that resolves when the update completes.
13517
+ * Cancel all unemitted event values for the given event type.
13518
+ * @param {string} type The event type.
13291
13519
  */
13292
- update() {
13293
- const { mc, indexer, clients, selection } = this;
13294
- const hasIndex = indexer?.index(clients);
13295
- return hasIndex ? indexer.update() : defaultUpdate(mc, clients, selection);
13520
+ cancel(type) {
13521
+ const entry = this._callbacks.get(type);
13522
+ entry?.queue.clear();
13296
13523
  }
13297
- };
13298
- function defaultUpdate(mc, clients, selection) {
13299
- return Promise.all(Array.from(clients).map((client) => {
13300
- const filter = selection.predicate(client);
13301
- if (filter != null) {
13302
- return mc.updateClient(client, client.query(filter));
13524
+ /**
13525
+ * Emit an event value to listeners for the given event type.
13526
+ * If a previous emit has not yet resolved, the event value
13527
+ * will be queued to be emitted later.
13528
+ * The actual event value given to listeners will be the result
13529
+ * of passing the input value through the emitValue() method.
13530
+ * @param {string} type The event type.
13531
+ * @param {*} value The event value.
13532
+ */
13533
+ emit(type, value) {
13534
+ const entry = this._callbacks.get(type) || {};
13535
+ if (entry.pending) {
13536
+ entry.queue.enqueue(value, this.emitQueueFilter(type, value));
13537
+ } else {
13538
+ const event = this.willEmit(type, value);
13539
+ const { callbacks, queue } = entry;
13540
+ if (callbacks?.size) {
13541
+ const callbackValues = Array.from(callbacks, (cb) => cb(event));
13542
+ entry.pending = Promise.allSettled(callbackValues).then(() => {
13543
+ entry.pending = null;
13544
+ if (!queue.isEmpty()) {
13545
+ this.emit(type, queue.dequeue());
13546
+ }
13547
+ });
13548
+ }
13303
13549
  }
13304
- }));
13305
- }
13306
-
13307
- // ../core/src/util/query-result.js
13308
- function queryResult() {
13309
- let resolve;
13310
- let reject;
13311
- const p = new Promise((r, e) => {
13312
- resolve = r;
13313
- reject = e;
13314
- });
13315
- return Object.assign(p, {
13316
- fulfill: (value) => (resolve(value), p),
13317
- reject: (err) => (reject(err), p)
13318
- });
13319
- }
13320
-
13321
- // ../core/src/QueryConsolidator.js
13322
- function wait(callback) {
13323
- const method = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : typeof setImmediate !== "undefined" ? setImmediate : setTimeout;
13324
- return method(callback);
13325
- }
13326
- function consolidator(enqueue, cache, record) {
13327
- let pending = [];
13328
- let id = 0;
13329
- function run() {
13330
- const groups = entryGroups(pending, cache);
13331
- pending = [];
13332
- id = 0;
13333
- for (const group of groups) {
13334
- consolidate(group, enqueue, record);
13335
- processResults(group, cache);
13336
- }
13337
- }
13338
- return {
13339
- add(entry, priority) {
13340
- if (entry.request.type === "arrow") {
13341
- id = id || wait(() => run());
13342
- pending.push({ entry, priority, index: pending.length });
13343
- } else {
13344
- enqueue(entry, priority);
13345
- }
13346
- }
13347
- };
13348
- }
13349
- function entryGroups(entries, cache) {
13350
- const groups = [];
13351
- const groupMap = /* @__PURE__ */ new Map();
13352
- for (const query of entries) {
13353
- const { entry: { request } } = query;
13354
- const key = consolidationKey(request.query, cache);
13355
- if (!groupMap.has(key)) {
13356
- const list = [];
13357
- groups.push(list);
13358
- groupMap.set(key, list);
13359
- }
13360
- groupMap.get(key).push(query);
13361
13550
  }
13362
- return groups;
13363
- }
13364
- function consolidationKey(query, cache) {
13365
- const sql2 = `${query}`;
13366
- if (query instanceof Query && !cache.get(sql2)) {
13367
- if (
13368
- // @ts-ignore
13369
- query.orderby().length || query.where().length || // @ts-ignore
13370
- query.qualify().length || query.having().length
13371
- ) {
13372
- return sql2;
13373
- }
13374
- const q = query.clone().$select("*");
13375
- const groupby = query.groupby();
13376
- if (groupby.length) {
13377
- const map = {};
13378
- query.select().forEach(({ as, expr }) => map[as] = expr);
13379
- q.$groupby(groupby.map((e) => e instanceof Ref && map[e.column] || e));
13380
- }
13381
- return `${q}`;
13382
- } else {
13383
- return sql2;
13551
+ };
13552
+ var DispatchQueue = class {
13553
+ /**
13554
+ * Create a new dispatch queue instance.
13555
+ */
13556
+ constructor() {
13557
+ this.clear();
13384
13558
  }
13385
- }
13386
- function consolidate(group, enqueue, record) {
13387
- if (shouldConsolidate(group)) {
13388
- enqueue({
13389
- request: {
13390
- type: "arrow",
13391
- cache: false,
13392
- record: false,
13393
- query: group.query = consolidatedQuery(group, record)
13394
- },
13395
- result: group.result = queryResult()
13396
- });
13397
- } else {
13398
- for (const { entry, priority } of group) {
13399
- enqueue(entry, priority);
13400
- }
13559
+ /**
13560
+ * Clear the queue state of all event values.
13561
+ */
13562
+ clear() {
13563
+ this.next = null;
13401
13564
  }
13402
- }
13403
- function shouldConsolidate(group) {
13404
- if (group.length > 1) {
13405
- const sql2 = `${group[0].entry.request.query}`;
13406
- for (let i = 1; i < group.length; ++i) {
13407
- if (sql2 !== `${group[i].entry.request.query}`) {
13408
- return true;
13409
- }
13410
- }
13565
+ /**
13566
+ * Indicate if the queue is empty.
13567
+ * @returns {boolean} True if queue is empty, false otherwise.
13568
+ */
13569
+ isEmpty() {
13570
+ return !this.next;
13411
13571
  }
13412
- return false;
13413
- }
13414
- function consolidatedQuery(group, record) {
13415
- const maps = group.maps = [];
13416
- const fields = /* @__PURE__ */ new Map();
13417
- for (const item of group) {
13418
- const { query: query2 } = item.entry.request;
13419
- const fieldMap = [];
13420
- maps.push(fieldMap);
13421
- for (const { as, expr } of query2.select()) {
13422
- const e = `${expr}`;
13423
- if (!fields.has(e)) {
13424
- fields.set(e, [`col${fields.size}`, expr]);
13572
+ /**
13573
+ * Add a new value to the queue, and optionally filter the
13574
+ * current queue content in response.
13575
+ * @param {*} value The value to add.
13576
+ * @param {(value: *) => boolean} [filter] An optional filter
13577
+ * function to apply to existing queue content. If unspecified
13578
+ * or falsy, all previously queued values are removed. Otherwise,
13579
+ * the provided function is applied to all queue entries. The
13580
+ * entry is retained if the filter function returns a truthy value,
13581
+ * otherwise the entry is removed.
13582
+ */
13583
+ enqueue(value, filter) {
13584
+ const tail = { value };
13585
+ if (filter && this.next) {
13586
+ let curr = this;
13587
+ while (curr.next) {
13588
+ if (filter(curr.next.value)) {
13589
+ curr = curr.next;
13590
+ } else {
13591
+ curr.next = curr.next.next;
13592
+ }
13425
13593
  }
13426
- const [name] = fields.get(e);
13427
- fieldMap.push([name, as]);
13594
+ curr.next = tail;
13595
+ } else {
13596
+ this.next = tail;
13428
13597
  }
13429
- record(`${query2}`);
13430
13598
  }
13431
- const query = group[0].entry.request.query.clone();
13432
- const groupby = query.groupby();
13433
- if (groupby.length) {
13434
- const map = {};
13435
- group.maps[0].forEach(([name, as]) => map[as] = name);
13436
- query.$groupby(groupby.map((e) => e instanceof Ref && map[e.column] || e));
13599
+ /**
13600
+ * Remove and return the next queued event value.
13601
+ * @returns {*} The next event value in the queue.
13602
+ */
13603
+ dequeue() {
13604
+ const { next } = this;
13605
+ this.next = next?.next;
13606
+ return next?.value;
13437
13607
  }
13438
- return query.$select(Array.from(fields.values()));
13608
+ };
13609
+
13610
+ // ../core/src/util/distinct.js
13611
+ function distinct(a, b) {
13612
+ return a === b ? false : a instanceof Date && b instanceof Date ? +a !== +b : Array.isArray(a) && Array.isArray(b) ? distinctArray(a, b) : true;
13439
13613
  }
13440
- async function processResults(group, cache) {
13441
- const { maps, query, result } = group;
13442
- if (!maps) return;
13443
- let data;
13444
- try {
13445
- data = await result;
13446
- } catch (err) {
13447
- for (const { entry } of group) {
13448
- entry.result.reject(err);
13449
- }
13450
- return;
13614
+ function distinctArray(a, b) {
13615
+ if (a.length !== b.length) return true;
13616
+ for (let i = 0; i < a.length; ++i) {
13617
+ if (a[i] !== b[i]) return true;
13451
13618
  }
13452
- const describe = isDescribeQuery(query);
13453
- group.forEach(({ entry }, index) => {
13454
- const { request, result: result2 } = entry;
13455
- const map = maps[index];
13456
- const extract = describe && map ? filterResult(data, map) : map ? projectResult(data, map) : data;
13457
- if (request.cache) {
13458
- cache.set(String(request.query), extract);
13459
- }
13460
- result2.fulfill(extract);
13461
- });
13619
+ return false;
13462
13620
  }
13463
- function projectResult(data, map) {
13464
- const cols = {};
13465
- for (const [name, as] of map) {
13466
- cols[as] = data.getChild(name);
13467
- }
13468
- return new data.constructor(cols);
13621
+
13622
+ // ../core/src/Param.js
13623
+ function isParam(x2) {
13624
+ return x2 instanceof Param;
13469
13625
  }
13470
- function filterResult(data, map) {
13471
- const lookup = new Map(map);
13472
- const result = [];
13473
- for (const d of data) {
13474
- if (lookup.has(d.column_name)) {
13475
- result.push({ ...d, column_name: lookup.get(d.column_name) });
13476
- }
13626
+ var Param = class _Param extends AsyncDispatch {
13627
+ /**
13628
+ * Create a new Param instance.
13629
+ * @param {*} value The initial value of the Param.
13630
+ */
13631
+ constructor(value) {
13632
+ super();
13633
+ this._value = value;
13477
13634
  }
13478
- return result;
13479
- }
13480
-
13481
- // ../core/src/util/cache.js
13482
- var requestIdle = typeof requestIdleCallback !== "undefined" ? requestIdleCallback : setTimeout;
13483
- var voidCache = () => ({
13484
- get: () => void 0,
13485
- set: (key, value) => value,
13486
- clear: () => {
13635
+ /**
13636
+ * Create a new Param instance with the given initial value.
13637
+ * @param {*} value The initial value of the Param.
13638
+ * @returns {Param} The new Param instance.
13639
+ */
13640
+ static value(value) {
13641
+ return new _Param(value);
13487
13642
  }
13488
- });
13489
- function lruCache({
13490
- max: max2 = 1e3,
13491
- // max entries
13492
- ttl = 3 * 60 * 60 * 1e3
13493
- // time-to-live, default 3 hours
13494
- } = {}) {
13495
- let cache = /* @__PURE__ */ new Map();
13496
- function evict() {
13497
- const expire = performance.now() - ttl;
13498
- let lruKey = null;
13499
- let lruLast = Infinity;
13500
- for (const [key, value] of cache) {
13501
- const { last: last2 } = value;
13502
- if (last2 < lruLast) {
13503
- lruKey = key;
13504
- lruLast = last2;
13505
- }
13506
- if (expire > last2) {
13507
- cache.delete(key);
13508
- }
13643
+ /**
13644
+ * Create a new Param instance over an array of initial values,
13645
+ * which may contain nested Params.
13646
+ * @param {*} values The initial values of the Param.
13647
+ * @returns {Param} The new Param instance.
13648
+ */
13649
+ static array(values) {
13650
+ if (values.some((v) => isParam(v))) {
13651
+ const p = new _Param();
13652
+ const update2 = () => {
13653
+ p.update(values.map((v) => isParam(v) ? v.value : v));
13654
+ };
13655
+ update2();
13656
+ values.forEach((v) => isParam(v) ? v.addEventListener("value", update2) : 0);
13657
+ return p;
13509
13658
  }
13510
- if (lruKey) {
13511
- cache.delete(lruKey);
13659
+ return new _Param(values);
13660
+ }
13661
+ /**
13662
+ * The current value of the Param.
13663
+ */
13664
+ get value() {
13665
+ return this._value;
13666
+ }
13667
+ /**
13668
+ * Update the Param value
13669
+ * @param {*} value The new value of the Param.
13670
+ * @param {object} [options] The update options.
13671
+ * @param {boolean} [options.force] A boolean flag indicating if the Param
13672
+ * should emit a 'value' event even if the internal value is unchanged.
13673
+ * @returns {this} This Param instance.
13674
+ */
13675
+ update(value, { force } = {}) {
13676
+ const shouldEmit = distinct(this._value, value) || force;
13677
+ if (shouldEmit) {
13678
+ this.emit("value", value);
13679
+ } else {
13680
+ this.cancel("value");
13512
13681
  }
13682
+ return this;
13513
13683
  }
13514
- return {
13515
- get(key) {
13516
- const entry = cache.get(key);
13517
- if (entry) {
13518
- entry.last = performance.now();
13519
- return entry.value;
13520
- }
13521
- },
13522
- set(key, value) {
13523
- cache.set(key, { last: performance.now(), value });
13524
- if (cache.size > max2) requestIdle(evict);
13525
- return value;
13526
- },
13527
- clear() {
13528
- cache = /* @__PURE__ */ new Map();
13684
+ /**
13685
+ * Upon value-typed updates, sets the current value to the input value
13686
+ * immediately prior to the event value being emitted to listeners.
13687
+ * @param {string} type The event type.
13688
+ * @param {*} value The input event value.
13689
+ * @returns {*} The input event value.
13690
+ */
13691
+ willEmit(type, value) {
13692
+ if (type === "value") {
13693
+ this._value = value;
13529
13694
  }
13530
- };
13531
- }
13695
+ return value;
13696
+ }
13697
+ };
13532
13698
 
13533
- // ../core/src/util/priority-queue.js
13534
- function priorityQueue(ranks) {
13535
- const queue = Array.from(
13536
- { length: ranks },
13537
- () => ({ head: null, tail: null })
13538
- );
13539
- return {
13540
- /**
13541
- * Indicate if the queue is empty.
13542
- * @returns [boolean] true if empty, false otherwise.
13543
- */
13544
- isEmpty() {
13545
- return queue.every((list) => !list.head);
13546
- },
13547
- /**
13548
- * Insert an item into the queue with a given priority rank.
13549
- * @param {*} item The item to add.
13550
- * @param {number} rank The integer priority rank.
13551
- * Priority ranks are integers starting at zero.
13552
- * Lower ranks indicate higher priority.
13553
- */
13554
- insert(item, rank2) {
13555
- const list = queue[rank2];
13556
- if (!list) {
13557
- throw new Error(`Invalid queue priority rank: ${rank2}`);
13558
- }
13559
- const node = { item, next: null };
13560
- if (list.head === null) {
13561
- list.head = list.tail = node;
13562
- } else {
13563
- list.tail = list.tail.next = node;
13564
- }
13565
- },
13566
- /**
13567
- * Remove a set of items from the queue, regardless of priority rank.
13568
- * If a provided item is not in the queue it will be ignored.
13569
- * @param {(item: *) => boolean} test A predicate function to test
13570
- * if an item should be removed (true to drop, false to keep).
13571
- */
13572
- remove(test) {
13573
- for (const list of queue) {
13574
- let { head, tail } = list;
13575
- for (let prev = null, curr = head; curr; prev = curr, curr = curr.next) {
13576
- if (test(curr.item)) {
13577
- if (curr === head) {
13578
- head = curr.next;
13579
- } else {
13580
- prev.next = curr.next;
13581
- }
13582
- if (curr === tail) tail = prev || head;
13583
- }
13584
- }
13585
- list.head = head;
13586
- list.tail = tail;
13587
- }
13588
- },
13589
- /**
13590
- * Remove and return the next highest priority item.
13591
- * @returns {*} The next item in the queue,
13592
- * or undefined if this queue is empty.
13593
- */
13594
- next() {
13595
- for (const list of queue) {
13596
- const { head } = list;
13597
- if (head !== null) {
13598
- list.head = head.next;
13599
- if (list.tail === head) {
13600
- list.tail = null;
13601
- }
13602
- return head.item;
13603
- }
13604
- }
13605
- }
13606
- };
13699
+ // ../core/src/Selection.js
13700
+ function isSelection(x2) {
13701
+ return x2 instanceof Selection;
13607
13702
  }
13608
-
13609
- // ../core/src/QueryManager.js
13610
- var Priority = { High: 0, Normal: 1, Low: 2 };
13611
- function QueryManager() {
13612
- const queue = priorityQueue(3);
13613
- let db;
13614
- let clientCache;
13615
- let logger;
13616
- let recorders = [];
13617
- let pending = null;
13618
- let consolidate2;
13619
- function next() {
13620
- if (pending || queue.isEmpty()) return;
13621
- const { request, result } = queue.next();
13622
- pending = submit(request, result);
13623
- pending.finally(() => {
13624
- pending = null;
13625
- next();
13626
- });
13703
+ var Selection = class _Selection extends Param {
13704
+ /**
13705
+ * Create a new Selection instance with an
13706
+ * intersect (conjunction) resolution strategy.
13707
+ * @param {object} [options] The selection options.
13708
+ * @param {boolean} [options.cross=false] Boolean flag indicating
13709
+ * cross-filtered resolution. If true, selection clauses will not
13710
+ * be applied to the clients they are associated with.
13711
+ * @returns {Selection} The new Selection instance.
13712
+ */
13713
+ static intersect({ cross = false } = {}) {
13714
+ return new _Selection(new SelectionResolver({ cross }));
13627
13715
  }
13628
- function enqueue(entry, priority = Priority.Normal) {
13629
- queue.insert(entry, priority);
13630
- next();
13716
+ /**
13717
+ * Create a new Selection instance with a
13718
+ * union (disjunction) resolution strategy.
13719
+ * @param {object} [options] The selection options.
13720
+ * @param {boolean} [options.cross=false] Boolean flag indicating
13721
+ * cross-filtered resolution. If true, selection clauses will not
13722
+ * be applied to the clients they are associated with.
13723
+ * @returns {Selection} The new Selection instance.
13724
+ */
13725
+ static union({ cross = false } = {}) {
13726
+ return new _Selection(new SelectionResolver({ cross, union: true }));
13631
13727
  }
13632
- function recordQuery(sql2) {
13633
- if (recorders.length && sql2) {
13634
- recorders.forEach((rec) => rec.add(sql2));
13635
- }
13728
+ /**
13729
+ * Create a new Selection instance with a singular resolution strategy
13730
+ * that keeps only the most recent selection clause.
13731
+ * @param {object} [options] The selection options.
13732
+ * @param {boolean} [options.cross=false] Boolean flag indicating
13733
+ * cross-filtered resolution. If true, selection clauses will not
13734
+ * be applied to the clients they are associated with.
13735
+ * @returns {Selection} The new Selection instance.
13736
+ */
13737
+ static single({ cross = false } = {}) {
13738
+ return new _Selection(new SelectionResolver({ cross, single: true }));
13636
13739
  }
13637
- async function submit(request, result) {
13638
- try {
13639
- const { query, type, cache = false, record = true, options } = request;
13640
- const sql2 = query ? `${query}` : null;
13641
- if (record) {
13642
- recordQuery(sql2);
13643
- }
13644
- if (cache) {
13645
- const cached = clientCache.get(sql2);
13646
- if (cached) {
13647
- logger.debug("Cache");
13648
- result.fulfill(cached);
13649
- return;
13650
- }
13651
- }
13652
- const t0 = performance.now();
13653
- const data = await db.query({ type, sql: sql2, ...options });
13654
- if (cache) clientCache.set(sql2, data);
13655
- logger.debug(`Request: ${(performance.now() - t0).toFixed(1)}`);
13656
- result.fulfill(data);
13657
- } catch (err) {
13658
- result.reject(err);
13659
- }
13740
+ /**
13741
+ * Create a new Selection instance with a
13742
+ * cross-filtered intersect resolution strategy.
13743
+ * @returns {Selection} The new Selection instance.
13744
+ */
13745
+ static crossfilter() {
13746
+ return new _Selection(new SelectionResolver({ cross: true }));
13660
13747
  }
13661
- return {
13662
- cache(value) {
13663
- return value !== void 0 ? clientCache = value === true ? lruCache() : value || voidCache() : clientCache;
13664
- },
13665
- logger(value) {
13666
- return value ? logger = value : logger;
13667
- },
13668
- connector(connector) {
13669
- return connector ? db = connector : db;
13670
- },
13671
- consolidate(flag) {
13672
- if (flag && !consolidate2) {
13673
- consolidate2 = consolidator(enqueue, clientCache, recordQuery);
13674
- } else if (!flag && consolidate2) {
13675
- consolidate2 = null;
13676
- }
13677
- },
13678
- request(request, priority = Priority.Normal) {
13679
- const result = queryResult();
13680
- const entry = { request, result };
13681
- if (consolidate2) {
13682
- consolidate2.add(entry, priority);
13683
- } else {
13684
- enqueue(entry, priority);
13685
- }
13686
- return result;
13687
- },
13688
- cancel(requests) {
13689
- const set = new Set(requests);
13690
- queue.remove(({ result }) => set.has(result));
13691
- },
13692
- clear() {
13693
- queue.remove(({ result }) => {
13694
- result.reject("Cleared");
13695
- return true;
13696
- });
13697
- },
13698
- record() {
13699
- let state = [];
13700
- const recorder = {
13701
- add(query) {
13702
- state.push(query);
13703
- },
13704
- reset() {
13705
- state = [];
13706
- },
13707
- snapshot() {
13708
- return state.slice();
13709
- },
13710
- stop() {
13711
- recorders = recorders.filter((x2) => x2 !== recorder);
13712
- return state;
13713
- }
13714
- };
13715
- recorders.push(recorder);
13716
- return recorder;
13717
- }
13718
- };
13719
- }
13720
-
13721
- // ../core/src/util/js-type.js
13722
- function jsType(type) {
13723
- switch (type) {
13724
- case "BIGINT":
13725
- case "HUGEINT":
13726
- case "INTEGER":
13727
- case "SMALLINT":
13728
- case "TINYINT":
13729
- case "UBIGINT":
13730
- case "UINTEGER":
13731
- case "USMALLINT":
13732
- case "UTINYINT":
13733
- case "DOUBLE":
13734
- case "FLOAT":
13735
- case "REAL":
13736
- return "number";
13737
- case "DATE":
13738
- case "TIMESTAMP":
13739
- case "TIMESTAMPTZ":
13740
- case "TIMESTAMP WITH TIME ZONE":
13741
- case "TIME":
13742
- case "TIMESTAMP_NS":
13743
- return "date";
13744
- case "BOOLEAN":
13745
- return "boolean";
13746
- case "VARCHAR":
13747
- case "UUID":
13748
- case "JSON":
13749
- return "string";
13750
- case "ARRAY":
13751
- case "LIST":
13752
- return "array";
13753
- case "BLOB":
13754
- case "STRUCT":
13755
- case "MAP":
13756
- case "GEOMETRY":
13757
- return "object";
13758
- default:
13759
- if (type.startsWith("DECIMAL")) {
13760
- return "number";
13761
- } else if (type.startsWith("STRUCT") || type.startsWith("MAP")) {
13762
- return "object";
13763
- } else if (type.endsWith("]")) {
13764
- return "array";
13765
- }
13766
- throw new Error(`Unsupported type: ${type}`);
13748
+ /**
13749
+ * Create a new Selection instance.
13750
+ * @param {SelectionResolver} resolver The selection resolution
13751
+ * strategy to apply.
13752
+ */
13753
+ constructor(resolver = new SelectionResolver()) {
13754
+ super([]);
13755
+ this._resolved = this._value;
13756
+ this._resolver = resolver;
13767
13757
  }
13768
- }
13769
-
13770
- // ../core/src/util/convert-arrow.js
13771
- function convertArrowValue(type) {
13772
- if (DataType.isTimestamp(type)) {
13773
- return (v) => v == null ? v : new Date(v);
13758
+ /**
13759
+ * Create a cloned copy of this Selection instance.
13760
+ * @returns {Selection} A clone of this selection.
13761
+ */
13762
+ clone() {
13763
+ const s = new _Selection(this._resolver);
13764
+ s._value = s._resolved = this._value;
13765
+ return s;
13774
13766
  }
13775
- if (DataType.isInt(type) && type.bitWidth >= 64) {
13776
- return (v) => v == null ? v : Number(v);
13767
+ /**
13768
+ * Create a clone of this Selection with clauses corresponding
13769
+ * to the provided source removed.
13770
+ * @param {*} source The clause source to remove.
13771
+ * @returns {Selection} A cloned and updated Selection.
13772
+ */
13773
+ remove(source) {
13774
+ const s = this.clone();
13775
+ s._value = s._resolved = s._resolver.resolve(this._resolved, { source });
13776
+ s._value.active = { source };
13777
+ return s;
13777
13778
  }
13778
- if (DataType.isDecimal(type)) {
13779
- const scale = 1 / Math.pow(10, type.scale);
13780
- return (v) => v == null ? v : decimalToNumber(v, scale);
13779
+ /**
13780
+ * The selection clause resolver.
13781
+ */
13782
+ get resolver() {
13783
+ return this._resolver;
13781
13784
  }
13782
- return (v) => v;
13783
- }
13784
- var BASE32 = Array.from(
13785
- { length: 8 },
13786
- (_, i) => Math.pow(2, i * 32)
13787
- );
13788
- function decimalToNumber(v, scale) {
13789
- const n = v.length;
13790
- let x2 = 0;
13791
- if (v.signed && (v[n - 1] | 0) < 0) {
13792
- for (let i = 0; i < n; ++i) {
13793
- x2 += ~v[i] * BASE32[i];
13794
- }
13795
- x2 = -(x2 + 1);
13796
- } else {
13797
- for (let i = 0; i < n; ++i) {
13798
- x2 += v[i] * BASE32[i];
13799
- }
13785
+ /**
13786
+ * Indicate if this selection has a single resolution strategy.
13787
+ */
13788
+ get single() {
13789
+ return this._resolver.single;
13800
13790
  }
13801
- return x2 * scale;
13802
- }
13803
-
13804
- // ../core/src/util/field-info.js
13805
- var Count = "count";
13806
- var Nulls = "nulls";
13807
- var Max = "max";
13808
- var Min = "min";
13809
- var Distinct = "distinct";
13810
- var statMap = {
13811
- [Count]: count,
13812
- [Distinct]: (column2) => count(column2).distinct(),
13813
- [Max]: max,
13814
- [Min]: min,
13815
- [Nulls]: (column2) => count().where(isNull(column2))
13816
- };
13817
- function summarize(table2, column2, stats) {
13818
- return Query.from(table2).select(Array.from(stats, (s) => [s, statMap[s](column2)]));
13819
- }
13820
- async function queryFieldInfo(mc, fields) {
13821
- if (fields.length === 1 && `${fields[0].column}` === "*") {
13822
- return getTableInfo(mc, fields[0].table);
13823
- } else {
13824
- return (await Promise.all(fields.map((f) => getFieldInfo(mc, f)))).filter((x2) => x2);
13791
+ /**
13792
+ * The current array of selection clauses.
13793
+ */
13794
+ get clauses() {
13795
+ return super.value;
13825
13796
  }
13826
- }
13827
- async function getFieldInfo(mc, { table: table2, column: column2, stats }) {
13828
- const q = Query.from({ source: table2 }).select({ column: column2 }).groupby(column2.aggregate ? sql`ALL` : []);
13829
- const [desc2] = Array.from(await mc.query(Query.describe(q)));
13830
- const info = {
13831
- table: table2,
13832
- column: `${column2}`,
13833
- sqlType: desc2.column_type,
13834
- type: jsType(desc2.column_type),
13835
- nullable: desc2.null === "YES"
13836
- };
13837
- if (!(stats?.length || stats?.size)) return info;
13838
- const result = await mc.query(
13839
- summarize(table2, column2, stats),
13840
- { persist: true }
13841
- );
13842
- for (let i = 0; i < result.numCols; ++i) {
13843
- const { name } = result.schema.fields[i];
13844
- const child = result.getChildAt(i);
13845
- const convert = convertArrowValue(child.type);
13846
- info[name] = convert(child.get(0));
13797
+ /**
13798
+ * The current active (most recently updated) selection clause.
13799
+ */
13800
+ get active() {
13801
+ return this.clauses.active;
13847
13802
  }
13848
- return info;
13849
- }
13850
- async function getTableInfo(mc, table2) {
13851
- const result = await mc.query(`DESCRIBE ${asRelation(table2)}`);
13852
- return Array.from(result).map((desc2) => ({
13853
- table: table2,
13854
- column: desc2.column_name,
13855
- sqlType: desc2.column_type,
13856
- type: jsType(desc2.column_type),
13857
- nullable: desc2.null === "YES"
13858
- }));
13859
- }
13860
-
13861
- // ../core/src/util/void-logger.js
13862
- function voidLogger() {
13863
- return {
13864
- debug() {
13865
- },
13866
- info() {
13867
- },
13868
- log() {
13869
- },
13870
- warn() {
13871
- },
13872
- error() {
13803
+ /**
13804
+ * The value corresponding to the current active selection clause.
13805
+ * This method ensures compatibility where a normal Param is expected.
13806
+ */
13807
+ get value() {
13808
+ return this.active?.value;
13809
+ }
13810
+ /**
13811
+ * The value corresponding to a given source. Returns undefined if
13812
+ * this selection does not include a clause from this source.
13813
+ * @param {*} source The clause source to look up the value for.
13814
+ */
13815
+ valueFor(source) {
13816
+ return this.clauses.find((c) => c.source === source)?.value;
13817
+ }
13818
+ /**
13819
+ * Emit an activate event with the given selection clause.
13820
+ * @param {*} clause The clause repesenting the potential activation.
13821
+ */
13822
+ activate(clause) {
13823
+ this.emit("activate", clause);
13824
+ }
13825
+ /**
13826
+ * Update the selection with a new selection clause.
13827
+ * @param {*} clause The selection clause to add.
13828
+ * @returns {this} This Selection instance.
13829
+ */
13830
+ update(clause) {
13831
+ this._resolved = this._resolver.resolve(this._resolved, clause, true);
13832
+ this._resolved.active = clause;
13833
+ return super.update(this._resolved);
13834
+ }
13835
+ /**
13836
+ * Upon value-typed updates, sets the current clause list to the
13837
+ * input value and returns the active clause value.
13838
+ * @param {string} type The event type.
13839
+ * @param {*} value The input event value.
13840
+ * @returns {*} For value-typed events, returns the active clause
13841
+ * values. Otherwise returns the input event value as-is.
13842
+ */
13843
+ willEmit(type, value) {
13844
+ if (type === "value") {
13845
+ this._value = value;
13846
+ return this.value;
13873
13847
  }
13874
- };
13875
- }
13876
-
13877
- // ../core/src/Coordinator.js
13878
- var _instance;
13879
- function coordinator(instance8) {
13880
- if (instance8) {
13881
- _instance = instance8;
13882
- } else if (_instance == null) {
13883
- _instance = new Coordinator();
13848
+ return value;
13849
+ }
13850
+ /**
13851
+ * Upon value-typed updates, returns a dispatch queue filter function.
13852
+ * The return value depends on the selection resolution strategy.
13853
+ * @param {string} type The event type.
13854
+ * @param {*} value The new event value that will be enqueued.
13855
+ * @returns {(value: *) => boolean|null} For value-typed events,
13856
+ * returns a dispatch queue filter function. Otherwise returns null.
13857
+ */
13858
+ emitQueueFilter(type, value) {
13859
+ return type === "value" ? this._resolver.queueFilter(value) : null;
13884
13860
  }
13885
- return _instance;
13886
- }
13887
- var Coordinator = class {
13888
- constructor(db = socketConnector(), options = {}) {
13889
- const {
13890
- logger = console,
13891
- manager = QueryManager()
13892
- } = options;
13893
- this.manager = manager;
13894
- this.logger(logger);
13895
- this.configure(options);
13896
- this.databaseConnector(db);
13897
- this.clear();
13861
+ /**
13862
+ * Indicates if a selection clause should not be applied to a given client.
13863
+ * The return value depends on the selection resolution strategy.
13864
+ * @param {*} client The selection clause.
13865
+ * @param {*} clause The client to test.
13866
+ * @returns True if the client should be skipped, false otherwise.
13867
+ */
13868
+ skip(client, clause) {
13869
+ return this._resolver.skip(client, clause);
13898
13870
  }
13899
- logger(logger) {
13900
- if (arguments.length) {
13901
- this._logger = logger || voidLogger();
13902
- this.manager.logger(this._logger);
13903
- }
13904
- return this._logger;
13871
+ /**
13872
+ * Return a selection query predicate for the given client.
13873
+ * @param {*} client The client whose data may be filtered.
13874
+ * @param {boolean} [noSkip=false] Disable skipping of active
13875
+ * cross-filtered sources. If set true, the source of the active
13876
+ * clause in a cross-filtered selection will not be skipped.
13877
+ * @returns {*} The query predicate for filtering client data,
13878
+ * based on the current state of this selection.
13879
+ */
13880
+ predicate(client, noSkip = false) {
13881
+ const { clauses } = this;
13882
+ const active = noSkip ? null : clauses.active;
13883
+ return this._resolver.predicate(clauses, active, client);
13905
13884
  }
13885
+ };
13886
+ var SelectionResolver = class {
13906
13887
  /**
13907
- * Set configuration options for this coordinator.
13908
- * @param {object} [options] Configration options.
13909
- * @param {boolean} [options.cache=true] Boolean flag to enable/disable query caching.
13910
- * @param {boolean} [options.consolidate=true] Boolean flag to enable/disable query consolidation.
13911
- * @param {boolean|object} [options.indexes=true] Boolean flag to enable/disable
13912
- * automatic data cube indexes or an index options object.
13888
+ * Create a new selection resolved instance.
13889
+ * @param {object} [options] The resolution strategy options.
13890
+ * @param {boolean} [options.union=false] Boolean flag to indicate a union strategy.
13891
+ * If false, an intersection strategy is used.
13892
+ * @param {boolean} [options.cross=false] Boolean flag to indicate cross-filtering.
13893
+ * @param {boolean} [options.single=false] Boolean flag to indicate single clauses only.
13913
13894
  */
13914
- configure({ cache = true, consolidate: consolidate2 = true, indexes = true } = {}) {
13915
- this.manager.cache(cache);
13916
- this.manager.consolidate(consolidate2);
13917
- this.indexes = indexes;
13895
+ constructor({ union, cross, single } = {}) {
13896
+ this.union = !!union;
13897
+ this.cross = !!cross;
13898
+ this.single = !!single;
13918
13899
  }
13919
- clear({ clients = true, cache = true } = {}) {
13920
- this.manager.clear();
13921
- if (clients) {
13922
- this.clients?.forEach((client) => this.disconnect(client));
13923
- this.filterGroups?.forEach((group) => group.finalize());
13924
- this.clients = /* @__PURE__ */ new Set();
13925
- this.filterGroups = /* @__PURE__ */ new Map();
13900
+ /**
13901
+ * Resolve a list of selection clauses according to the resolution strategy.
13902
+ * @param {*[]} clauseList An array of selection clauses.
13903
+ * @param {*} clause A new selection clause to add.
13904
+ * @returns {*[]} An updated array of selection clauses.
13905
+ */
13906
+ resolve(clauseList, clause, reset = false) {
13907
+ const { source, predicate } = clause;
13908
+ const filtered = clauseList.filter((c) => source !== c.source);
13909
+ const clauses = this.single ? [] : filtered;
13910
+ if (this.single && reset) filtered.forEach((c) => c.source?.reset?.());
13911
+ if (predicate) clauses.push(clause);
13912
+ return clauses;
13913
+ }
13914
+ /**
13915
+ * Indicates if a selection clause should not be applied to a given client.
13916
+ * The return value depends on the resolution strategy.
13917
+ * @param {*} client The selection clause.
13918
+ * @param {*} clause The client to test.
13919
+ * @returns True if the client should be skipped, false otherwise.
13920
+ */
13921
+ skip(client, clause) {
13922
+ return this.cross && clause?.clients?.has(client);
13923
+ }
13924
+ /**
13925
+ * Return a selection query predicate for the given client.
13926
+ * @param {*[]} clauseList An array of selection clauses.
13927
+ * @param {*} active The current active selection clause.
13928
+ * @param {*} client The client whose data may be filtered.
13929
+ * @returns {*} The query predicate for filtering client data,
13930
+ * based on the current state of this selection.
13931
+ */
13932
+ predicate(clauseList, active, client) {
13933
+ const { union } = this;
13934
+ if (this.skip(client, active)) return void 0;
13935
+ const predicates = clauseList.filter((clause) => !this.skip(client, clause)).map((clause) => clause.predicate);
13936
+ return union && predicates.length > 1 ? or(predicates) : predicates;
13937
+ }
13938
+ /**
13939
+ * Returns a filter function for queued selection updates.
13940
+ * @param {*} value The new event value that will be enqueued.
13941
+ * @returns {(value: *) => boolean|null} A dispatch queue filter
13942
+ * function, or null if all unemitted event values should be filtered.
13943
+ */
13944
+ queueFilter(value) {
13945
+ if (this.cross) {
13946
+ const source = value.active?.source;
13947
+ return (clauses) => clauses.active?.source !== source;
13926
13948
  }
13927
- if (cache) this.manager.cache().clear();
13949
+ return null;
13928
13950
  }
13929
- databaseConnector(db) {
13930
- return this.manager.connector(db);
13951
+ };
13952
+
13953
+ // ../core/src/FilterGroup.js
13954
+ var FilterGroup = class {
13955
+ /**
13956
+ * @param {Coordinator} coordinator The Mosaic coordinator.
13957
+ * @param {Selection} selection The shared filter selection.
13958
+ * @param {object|boolean} index Boolean flag or options hash for
13959
+ * a data cube indexer. Falsy values disable indexing.
13960
+ */
13961
+ constructor(coordinator2, selection, index = true) {
13962
+ this.mc = coordinator2;
13963
+ this.selection = selection;
13964
+ this.clients = /* @__PURE__ */ new Set();
13965
+ this.indexer = null;
13966
+ this.index(index);
13967
+ const { value, activate } = this.handlers = {
13968
+ value: () => this.update(),
13969
+ activate: (clause) => {
13970
+ this.indexer?.index(this.clients, clause);
13971
+ }
13972
+ };
13973
+ selection.addEventListener("value", value);
13974
+ selection.addEventListener("activate", activate);
13931
13975
  }
13932
- // -- Query Management ----
13933
- cancel(requests) {
13934
- this.manager.cancel(requests);
13976
+ finalize() {
13977
+ const { value, activate } = this.handlers;
13978
+ this.selection.removeEventListener("value", value);
13979
+ this.selection.removeEventListener("activate", activate);
13935
13980
  }
13936
- exec(query, { priority = Priority.Normal } = {}) {
13937
- query = Array.isArray(query) ? query.join(";\n") : query;
13938
- return this.manager.request({ type: "exec", query }, priority);
13981
+ index(state) {
13982
+ const { selection } = this;
13983
+ const { resolver } = selection;
13984
+ this.indexer = state && (resolver.single || !resolver.union) ? new DataCubeIndexer(this.mc, { ...state, selection }) : null;
13939
13985
  }
13940
- query(query, {
13941
- type = "arrow",
13942
- cache = true,
13943
- priority = Priority.Normal,
13944
- ...options
13945
- } = {}) {
13946
- return this.manager.request({ type, query, cache, options }, priority);
13986
+ reset() {
13987
+ this.indexer?.reset();
13947
13988
  }
13948
- prefetch(query, options = {}) {
13949
- return this.query(query, { ...options, cache: true, priority: Priority.Low });
13989
+ add(client) {
13990
+ (this.clients = new Set(this.clients)).add(client);
13991
+ return this;
13950
13992
  }
13951
- createBundle(name, queries, priority = Priority.Low) {
13952
- const options = { name, queries };
13953
- return this.manager.request({ type: "create-bundle", options }, priority);
13993
+ remove(client) {
13994
+ if (this.clients.has(client)) {
13995
+ (this.clients = new Set(this.clients)).delete(client);
13996
+ }
13997
+ return this;
13954
13998
  }
13955
- loadBundle(name, priority = Priority.High) {
13956
- const options = { name };
13957
- return this.manager.request({ type: "load-bundle", options }, priority);
13999
+ /**
14000
+ * Internal method to process a selection update.
14001
+ * The return value is passed as a selection callback value.
14002
+ * @returns {Promise} A Promise that resolves when the update completes.
14003
+ */
14004
+ update() {
14005
+ const { mc, indexer, clients, selection } = this;
14006
+ const hasIndex = indexer?.index(clients);
14007
+ return hasIndex ? indexer.update() : defaultUpdate(mc, clients, selection);
13958
14008
  }
13959
- // -- Client Management ----
13960
- updateClient(client, query, priority = Priority.Normal) {
13961
- client.queryPending();
13962
- return this.query(query, { priority }).then(
13963
- (data) => client.queryResult(data).update(),
13964
- (err) => {
13965
- client.queryError(err);
13966
- this._logger.error(err);
13967
- }
13968
- );
14009
+ };
14010
+ function defaultUpdate(mc, clients, selection) {
14011
+ return Promise.all(Array.from(clients).map((client) => {
14012
+ const filter = selection.predicate(client);
14013
+ if (filter != null) {
14014
+ return mc.updateClient(client, client.query(filter));
14015
+ }
14016
+ }));
14017
+ }
14018
+
14019
+ // ../core/src/util/query-result.js
14020
+ function queryResult() {
14021
+ let resolve;
14022
+ let reject;
14023
+ const p = new Promise((r, e) => {
14024
+ resolve = r;
14025
+ reject = e;
14026
+ });
14027
+ return Object.assign(p, {
14028
+ fulfill: (value) => (resolve(value), p),
14029
+ reject: (err) => (reject(err), p)
14030
+ });
14031
+ }
14032
+
14033
+ // ../core/src/QueryConsolidator.js
14034
+ function wait(callback) {
14035
+ const method = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : typeof setImmediate !== "undefined" ? setImmediate : setTimeout;
14036
+ return method(callback);
14037
+ }
14038
+ function consolidator(enqueue, cache, record) {
14039
+ let pending = [];
14040
+ let id = 0;
14041
+ function run() {
14042
+ const groups = entryGroups(pending, cache);
14043
+ pending = [];
14044
+ id = 0;
14045
+ for (const group of groups) {
14046
+ consolidate(group, enqueue, record);
14047
+ processResults(group, cache);
14048
+ }
13969
14049
  }
13970
- requestQuery(client, query) {
13971
- this.filterGroups.get(client.filterBy)?.reset();
13972
- return query ? this.updateClient(client, query) : client.update();
14050
+ return {
14051
+ add(entry, priority) {
14052
+ if (entry.request.type === "arrow") {
14053
+ id = id || wait(() => run());
14054
+ pending.push({ entry, priority, index: pending.length });
14055
+ } else {
14056
+ enqueue(entry, priority);
14057
+ }
14058
+ }
14059
+ };
14060
+ }
14061
+ function entryGroups(entries, cache) {
14062
+ const groups = [];
14063
+ const groupMap = /* @__PURE__ */ new Map();
14064
+ for (const query of entries) {
14065
+ const { entry: { request } } = query;
14066
+ const key = consolidationKey(request.query, cache);
14067
+ if (!groupMap.has(key)) {
14068
+ const list = [];
14069
+ groups.push(list);
14070
+ groupMap.set(key, list);
14071
+ }
14072
+ groupMap.get(key).push(query);
13973
14073
  }
13974
- /**
13975
- * Connect a client to the coordinator.
13976
- * @param {import('./MosaicClient.js').MosaicClient} client the client to disconnect
13977
- */
13978
- async connect(client) {
13979
- const { clients, filterGroups, indexes } = this;
13980
- if (clients.has(client)) {
13981
- throw new Error("Client already connected.");
14074
+ return groups;
14075
+ }
14076
+ function consolidationKey(query, cache) {
14077
+ const sql2 = `${query}`;
14078
+ if (query instanceof Query && !cache.get(sql2)) {
14079
+ if (
14080
+ // @ts-ignore
14081
+ query.orderby().length || query.where().length || // @ts-ignore
14082
+ query.qualify().length || query.having().length
14083
+ ) {
14084
+ return sql2;
13982
14085
  }
13983
- clients.add(client);
13984
- client.coordinator = this;
13985
- const fields = client.fields();
13986
- if (fields?.length) {
13987
- client.fieldInfo(await queryFieldInfo(this, fields));
14086
+ const q = query.clone().$select("*");
14087
+ const groupby = query.groupby();
14088
+ if (groupby.length) {
14089
+ const map = {};
14090
+ query.select().forEach(({ as, expr }) => map[as] = expr);
14091
+ q.$groupby(groupby.map((e) => e instanceof Ref && map[e.column] || e));
13988
14092
  }
13989
- const filter = client.filterBy;
13990
- if (filter) {
13991
- if (filterGroups.has(filter)) {
13992
- filterGroups.get(filter).add(client);
13993
- } else {
13994
- const group = new FilterGroup(this, filter, indexes);
13995
- filterGroups.set(filter, group.add(client));
14093
+ return `${q}`;
14094
+ } else {
14095
+ return sql2;
14096
+ }
14097
+ }
14098
+ function consolidate(group, enqueue, record) {
14099
+ if (shouldConsolidate(group)) {
14100
+ enqueue({
14101
+ request: {
14102
+ type: "arrow",
14103
+ cache: false,
14104
+ record: false,
14105
+ query: group.query = consolidatedQuery(group, record)
14106
+ },
14107
+ result: group.result = queryResult()
14108
+ });
14109
+ } else {
14110
+ for (const { entry, priority } of group) {
14111
+ enqueue(entry, priority);
14112
+ }
14113
+ }
14114
+ }
14115
+ function shouldConsolidate(group) {
14116
+ if (group.length > 1) {
14117
+ const sql2 = `${group[0].entry.request.query}`;
14118
+ for (let i = 1; i < group.length; ++i) {
14119
+ if (sql2 !== `${group[i].entry.request.query}`) {
14120
+ return true;
13996
14121
  }
13997
14122
  }
13998
- client.requestQuery();
13999
14123
  }
14000
- /**
14001
- * Disconnect a client from the coordinator.
14002
- *
14003
- * @param {import('./MosaicClient.js').MosaicClient} client the client to disconnect
14004
- */
14005
- disconnect(client) {
14006
- const { clients, filterGroups } = this;
14007
- if (!clients.has(client)) return;
14008
- clients.delete(client);
14009
- filterGroups.get(client.filterBy)?.remove(client);
14010
- client.coordinator = null;
14124
+ return false;
14125
+ }
14126
+ function consolidatedQuery(group, record) {
14127
+ const maps = group.maps = [];
14128
+ const fields = /* @__PURE__ */ new Map();
14129
+ for (const item of group) {
14130
+ const { query: query2 } = item.entry.request;
14131
+ const fieldMap = [];
14132
+ maps.push(fieldMap);
14133
+ for (const { as, expr } of query2.select()) {
14134
+ const e = `${expr}`;
14135
+ if (!fields.has(e)) {
14136
+ fields.set(e, [`col${fields.size}`, expr]);
14137
+ }
14138
+ const [name] = fields.get(e);
14139
+ fieldMap.push([name, as]);
14140
+ }
14141
+ record(`${query2}`);
14011
14142
  }
14012
- };
14013
-
14014
- // ../core/src/util/AsyncDispatch.js
14015
- var AsyncDispatch = class {
14016
- /**
14017
- * Create a new asynchronous dispatcher instance.
14018
- */
14019
- constructor() {
14020
- this._callbacks = /* @__PURE__ */ new Map();
14143
+ const query = group[0].entry.request.query.clone();
14144
+ const groupby = query.groupby();
14145
+ if (groupby.length) {
14146
+ const map = {};
14147
+ group.maps[0].forEach(([name, as]) => map[as] = name);
14148
+ query.$groupby(groupby.map((e) => e instanceof Ref && map[e.column] || e));
14021
14149
  }
14022
- /**
14023
- * Add an event listener callback for the provided event type.
14024
- * @param {string} type The event type.
14025
- * @param {(value: *) => void | Promise} callback The event handler
14026
- * callback function to add. If the callback has already been
14027
- * added for the event type, this method has no effect.
14028
- */
14029
- addEventListener(type, callback) {
14030
- if (!this._callbacks.has(type)) {
14031
- this._callbacks.set(type, {
14032
- callbacks: /* @__PURE__ */ new Set(),
14033
- pending: null,
14034
- queue: new DispatchQueue()
14035
- });
14150
+ return query.$select(Array.from(fields.values()));
14151
+ }
14152
+ async function processResults(group, cache) {
14153
+ const { maps, query, result } = group;
14154
+ if (!maps) return;
14155
+ let data;
14156
+ try {
14157
+ data = await result;
14158
+ } catch (err) {
14159
+ for (const { entry } of group) {
14160
+ entry.result.reject(err);
14036
14161
  }
14037
- const entry = this._callbacks.get(type);
14038
- entry.callbacks.add(callback);
14162
+ return;
14039
14163
  }
14040
- /**
14041
- * Remove an event listener callback for the provided event type.
14042
- * @param {string} type The event type.
14043
- * @param {(value: *) => void | Promise} callback The event handler
14044
- * callback function to remove.
14045
- */
14046
- removeEventListener(type, callback) {
14047
- const entry = this._callbacks.get(type);
14048
- if (entry) {
14049
- entry.callbacks.delete(callback);
14164
+ const describe = isDescribeQuery(query);
14165
+ group.forEach(({ entry }, index) => {
14166
+ const { request, result: result2 } = entry;
14167
+ const map = maps[index];
14168
+ const extract = describe && map ? filterResult(data, map) : map ? projectResult(data, map) : data;
14169
+ if (request.cache) {
14170
+ cache.set(String(request.query), extract);
14050
14171
  }
14172
+ result2.fulfill(extract);
14173
+ });
14174
+ }
14175
+ function projectResult(data, map) {
14176
+ const cols = {};
14177
+ for (const [name, as] of map) {
14178
+ cols[as] = data.getChild(name);
14051
14179
  }
14052
- /**
14053
- * Lifecycle method that returns the event value to emit.
14054
- * This default implementation simply returns the input value as-is.
14055
- * Subclasses may override this method to implement custom transformations
14056
- * prior to emitting an event value to all listeners.
14057
- * @param {string} type The event type.
14058
- * @param {*} value The event value.
14059
- * @returns The (possibly transformed) event value to emit.
14060
- */
14061
- willEmit(type, value) {
14062
- return value;
14063
- }
14064
- /**
14065
- * Lifecycle method that returns a filter function for updating the
14066
- * queue of unemitted event values prior to enqueueing a new value.
14067
- * This default implementation simply returns null, indicating that
14068
- * any other unemitted event values should be dropped (that is, all
14069
- * queued events are filtered)
14070
- * @param {string} type The event type.
14071
- * @param {*} value The new event value that will be enqueued.
14072
- * @returns {(value: *) => boolean|null} A dispatch queue filter
14073
- * function, or null if all unemitted event values should be filtered.
14074
- */
14075
- emitQueueFilter(type, value) {
14076
- return null;
14180
+ return new data.constructor(cols);
14181
+ }
14182
+ function filterResult(data, map) {
14183
+ const lookup = new Map(map);
14184
+ const result = [];
14185
+ for (const d of data) {
14186
+ if (lookup.has(d.column_name)) {
14187
+ result.push({ ...d, column_name: lookup.get(d.column_name) });
14188
+ }
14077
14189
  }
14078
- /**
14079
- * Cancel all unemitted event values for the given event type.
14080
- * @param {string} type The event type.
14081
- */
14082
- cancel(type) {
14083
- const entry = this._callbacks.get(type);
14084
- entry?.queue.clear();
14190
+ return result;
14191
+ }
14192
+
14193
+ // ../core/src/util/cache.js
14194
+ var requestIdle = typeof requestIdleCallback !== "undefined" ? requestIdleCallback : setTimeout;
14195
+ var voidCache = () => ({
14196
+ get: () => void 0,
14197
+ set: (key, value) => value,
14198
+ clear: () => {
14085
14199
  }
14086
- /**
14087
- * Emit an event value to listeners for the given event type.
14088
- * If a previous emit has not yet resolved, the event value
14089
- * will be queued to be emitted later.
14090
- * The actual event value given to listeners will be the result
14091
- * of passing the input value through the emitValue() method.
14092
- * @param {string} type The event type.
14093
- * @param {*} value The event value.
14094
- */
14095
- emit(type, value) {
14096
- const entry = this._callbacks.get(type) || {};
14097
- if (entry.pending) {
14098
- entry.queue.enqueue(value, this.emitQueueFilter(type, value));
14099
- } else {
14100
- const event = this.willEmit(type, value);
14101
- const { callbacks, queue } = entry;
14102
- if (callbacks?.size) {
14103
- const callbackValues = Array.from(callbacks, (cb) => cb(event));
14104
- entry.pending = Promise.allSettled(callbackValues).then(() => {
14105
- entry.pending = null;
14106
- if (!queue.isEmpty()) {
14107
- this.emit(type, queue.dequeue());
14108
- }
14109
- });
14200
+ });
14201
+ function lruCache({
14202
+ max: max2 = 1e3,
14203
+ // max entries
14204
+ ttl = 3 * 60 * 60 * 1e3
14205
+ // time-to-live, default 3 hours
14206
+ } = {}) {
14207
+ let cache = /* @__PURE__ */ new Map();
14208
+ function evict() {
14209
+ const expire = performance.now() - ttl;
14210
+ let lruKey = null;
14211
+ let lruLast = Infinity;
14212
+ for (const [key, value] of cache) {
14213
+ const { last: last2 } = value;
14214
+ if (last2 < lruLast) {
14215
+ lruKey = key;
14216
+ lruLast = last2;
14217
+ }
14218
+ if (expire > last2) {
14219
+ cache.delete(key);
14110
14220
  }
14111
14221
  }
14222
+ if (lruKey) {
14223
+ cache.delete(lruKey);
14224
+ }
14112
14225
  }
14113
- };
14114
- var DispatchQueue = class {
14115
- /**
14116
- * Create a new dispatch queue instance.
14117
- */
14226
+ return {
14227
+ get(key) {
14228
+ const entry = cache.get(key);
14229
+ if (entry) {
14230
+ entry.last = performance.now();
14231
+ return entry.value;
14232
+ }
14233
+ },
14234
+ set(key, value) {
14235
+ cache.set(key, { last: performance.now(), value });
14236
+ if (cache.size > max2) requestIdle(evict);
14237
+ return value;
14238
+ },
14239
+ clear() {
14240
+ cache = /* @__PURE__ */ new Map();
14241
+ }
14242
+ };
14243
+ }
14244
+
14245
+ // ../core/src/util/priority-queue.js
14246
+ function priorityQueue(ranks) {
14247
+ const queue = Array.from(
14248
+ { length: ranks },
14249
+ () => ({ head: null, tail: null })
14250
+ );
14251
+ return {
14252
+ /**
14253
+ * Indicate if the queue is empty.
14254
+ * @returns [boolean] true if empty, false otherwise.
14255
+ */
14256
+ isEmpty() {
14257
+ return queue.every((list) => !list.head);
14258
+ },
14259
+ /**
14260
+ * Insert an item into the queue with a given priority rank.
14261
+ * @param {*} item The item to add.
14262
+ * @param {number} rank The integer priority rank.
14263
+ * Priority ranks are integers starting at zero.
14264
+ * Lower ranks indicate higher priority.
14265
+ */
14266
+ insert(item, rank2) {
14267
+ const list = queue[rank2];
14268
+ if (!list) {
14269
+ throw new Error(`Invalid queue priority rank: ${rank2}`);
14270
+ }
14271
+ const node = { item, next: null };
14272
+ if (list.head === null) {
14273
+ list.head = list.tail = node;
14274
+ } else {
14275
+ list.tail = list.tail.next = node;
14276
+ }
14277
+ },
14278
+ /**
14279
+ * Remove a set of items from the queue, regardless of priority rank.
14280
+ * If a provided item is not in the queue it will be ignored.
14281
+ * @param {(item: *) => boolean} test A predicate function to test
14282
+ * if an item should be removed (true to drop, false to keep).
14283
+ */
14284
+ remove(test) {
14285
+ for (const list of queue) {
14286
+ let { head, tail } = list;
14287
+ for (let prev = null, curr = head; curr; prev = curr, curr = curr.next) {
14288
+ if (test(curr.item)) {
14289
+ if (curr === head) {
14290
+ head = curr.next;
14291
+ } else {
14292
+ prev.next = curr.next;
14293
+ }
14294
+ if (curr === tail) tail = prev || head;
14295
+ }
14296
+ }
14297
+ list.head = head;
14298
+ list.tail = tail;
14299
+ }
14300
+ },
14301
+ /**
14302
+ * Remove and return the next highest priority item.
14303
+ * @returns {*} The next item in the queue,
14304
+ * or undefined if this queue is empty.
14305
+ */
14306
+ next() {
14307
+ for (const list of queue) {
14308
+ const { head } = list;
14309
+ if (head !== null) {
14310
+ list.head = head.next;
14311
+ if (list.tail === head) {
14312
+ list.tail = null;
14313
+ }
14314
+ return head.item;
14315
+ }
14316
+ }
14317
+ }
14318
+ };
14319
+ }
14320
+
14321
+ // ../core/src/QueryManager.js
14322
+ var Priority = { High: 0, Normal: 1, Low: 2 };
14323
+ var QueryManager = class {
14118
14324
  constructor() {
14119
- this.clear();
14325
+ this.queue = priorityQueue(3);
14326
+ this.db = null;
14327
+ this.clientCache = null;
14328
+ this._logger = null;
14329
+ this._logQueries = false;
14330
+ this.recorders = [];
14331
+ this.pending = null;
14332
+ this._consolidate = null;
14120
14333
  }
14121
- /**
14122
- * Clear the queue state of all event values.
14123
- */
14124
- clear() {
14125
- this.next = null;
14334
+ next() {
14335
+ if (this.pending || this.queue.isEmpty()) return;
14336
+ const { request, result } = this.queue.next();
14337
+ this.pending = this.submit(request, result);
14338
+ this.pending.finally(() => {
14339
+ this.pending = null;
14340
+ this.next();
14341
+ });
14126
14342
  }
14127
- /**
14128
- * Indicate if the queue is empty.
14129
- * @returns {boolean} True if queue is empty, false otherwise.
14130
- */
14131
- isEmpty() {
14132
- return !this.next;
14343
+ enqueue(entry, priority = Priority.Normal) {
14344
+ this.queue.insert(entry, priority);
14345
+ this.next();
14133
14346
  }
14134
- /**
14135
- * Add a new value to the queue, and optionally filter the
14136
- * current queue content in response.
14137
- * @param {*} value The value to add.
14138
- * @param {(value: *) => boolean} [filter] An optional filter
14139
- * function to apply to existing queue content. If unspecified
14140
- * or falsy, all previously queued values are removed. Otherwise,
14141
- * the provided function is applied to all queue entries. The
14142
- * entry is retained if the filter function returns a truthy value,
14143
- * otherwise the entry is removed.
14144
- */
14145
- enqueue(value, filter) {
14146
- const tail = { value };
14147
- if (filter && this.next) {
14148
- let curr = this;
14149
- while (curr.next) {
14150
- if (filter(curr.next.value)) {
14151
- curr = curr.next;
14152
- } else {
14153
- curr.next = curr.next.next;
14347
+ recordQuery(sql2) {
14348
+ if (this.recorders.length && sql2) {
14349
+ this.recorders.forEach((rec) => rec.add(sql2));
14350
+ }
14351
+ }
14352
+ async submit(request, result) {
14353
+ try {
14354
+ const { query, type, cache = false, record = true, options } = request;
14355
+ const sql2 = query ? `${query}` : null;
14356
+ if (record) {
14357
+ this.recordQuery(sql2);
14358
+ }
14359
+ if (cache) {
14360
+ const cached = this.clientCache.get(sql2);
14361
+ if (cached) {
14362
+ this._logger.debug("Cache");
14363
+ result.fulfill(cached);
14364
+ return;
14154
14365
  }
14155
14366
  }
14156
- curr.next = tail;
14157
- } else {
14158
- this.next = tail;
14367
+ const t0 = performance.now();
14368
+ if (this._logQueries) {
14369
+ this._logger.debug("Query", { type, sql: sql2, ...options });
14370
+ }
14371
+ const data = await this.db.query({ type, sql: sql2, ...options });
14372
+ if (cache) this.clientCache.set(sql2, data);
14373
+ this._logger.debug(`Request: ${(performance.now() - t0).toFixed(1)}`);
14374
+ result.fulfill(data);
14375
+ } catch (err) {
14376
+ result.reject(err);
14159
14377
  }
14160
14378
  }
14161
- /**
14162
- * Remove and return the next queued event value.
14163
- * @returns {*} The next event value in the queue.
14164
- */
14165
- dequeue() {
14166
- const { next } = this;
14167
- this.next = next?.next;
14168
- return next?.value;
14379
+ cache(value) {
14380
+ return value !== void 0 ? this.clientCache = value === true ? lruCache() : value || voidCache() : this.clientCache;
14169
14381
  }
14170
- };
14171
-
14172
- // ../core/src/util/distinct.js
14173
- function distinct(a, b) {
14174
- return a === b ? false : a instanceof Date && b instanceof Date ? +a !== +b : Array.isArray(a) && Array.isArray(b) ? distinctArray(a, b) : true;
14175
- }
14176
- function distinctArray(a, b) {
14177
- if (a.length !== b.length) return true;
14178
- for (let i = 0; i < a.length; ++i) {
14179
- if (a[i] !== b[i]) return true;
14382
+ logger(value) {
14383
+ return value ? this._logger = value : this._logger;
14180
14384
  }
14181
- return false;
14182
- }
14183
-
14184
- // ../core/src/Param.js
14185
- function isParam(x2) {
14186
- return x2 instanceof Param;
14187
- }
14188
- var Param = class _Param extends AsyncDispatch {
14189
- /**
14190
- * Create a new Param instance.
14191
- * @param {*} value The initial value of the Param.
14192
- */
14193
- constructor(value) {
14194
- super();
14195
- this._value = value;
14385
+ logQueries(value) {
14386
+ return value !== void 0 ? this._logQueries = !!value : this._logQueries;
14196
14387
  }
14197
- /**
14198
- * Create a new Param instance with the given initial value.
14199
- * @param {*} value The initial value of the Param.
14200
- * @returns {Param} The new Param instance.
14201
- */
14202
- static value(value) {
14203
- return new _Param(value);
14388
+ connector(connector) {
14389
+ return connector ? this.db = connector : this.db;
14204
14390
  }
14205
- /**
14206
- * Create a new Param instance over an array of initial values,
14207
- * which may contain nested Params.
14208
- * @param {*} values The initial values of the Param.
14209
- * @returns {Param} The new Param instance.
14210
- */
14211
- static array(values) {
14212
- if (values.some((v) => isParam(v))) {
14213
- const p = new _Param();
14214
- const update2 = () => {
14215
- p.update(values.map((v) => isParam(v) ? v.value : v));
14216
- };
14217
- update2();
14218
- values.forEach((v) => isParam(v) ? v.addEventListener("value", update2) : 0);
14219
- return p;
14391
+ consolidate(flag) {
14392
+ if (flag && !this._consolidate) {
14393
+ this._consolidate = consolidator(this.enqueue.bind(this), this.clientCache, this.recordQuery.bind(this));
14394
+ } else if (!flag && this._consolidate) {
14395
+ this._consolidate = null;
14220
14396
  }
14221
- return new _Param(values);
14222
14397
  }
14223
- /**
14224
- * The current value of the Param.
14225
- */
14226
- get value() {
14227
- return this._value;
14228
- }
14229
- /**
14230
- * Update the Param value
14231
- * @param {*} value The new value of the Param.
14232
- * @param {object} [options] The update options.
14233
- * @param {boolean} [options.force] A boolean flag indicating if the Param
14234
- * should emit a 'value' event even if the internal value is unchanged.
14235
- * @returns {this} This Param instance.
14236
- */
14237
- update(value, { force } = {}) {
14238
- const shouldEmit = distinct(this._value, value) || force;
14239
- if (shouldEmit) {
14240
- this.emit("value", value);
14398
+ request(request, priority = Priority.Normal) {
14399
+ const result = queryResult();
14400
+ const entry = { request, result };
14401
+ if (this._consolidate) {
14402
+ this._consolidate.add(entry, priority);
14241
14403
  } else {
14242
- this.cancel("value");
14404
+ this.enqueue(entry, priority);
14243
14405
  }
14244
- return this;
14406
+ return result;
14245
14407
  }
14246
- /**
14247
- * Upon value-typed updates, sets the current value to the input value
14248
- * immediately prior to the event value being emitted to listeners.
14249
- * @param {string} type The event type.
14250
- * @param {*} value The input event value.
14251
- * @returns {*} The input event value.
14252
- */
14253
- willEmit(type, value) {
14254
- if (type === "value") {
14255
- this._value = value;
14256
- }
14257
- return value;
14408
+ cancel(requests) {
14409
+ const set = new Set(requests);
14410
+ this.queue.remove(({ result }) => set.has(result));
14411
+ }
14412
+ clear() {
14413
+ this.queue.remove(({ result }) => {
14414
+ result.reject("Cleared");
14415
+ return true;
14416
+ });
14417
+ }
14418
+ record() {
14419
+ let state = [];
14420
+ const recorder = {
14421
+ add(query) {
14422
+ state.push(query);
14423
+ },
14424
+ reset() {
14425
+ state = [];
14426
+ },
14427
+ snapshot() {
14428
+ return state.slice();
14429
+ },
14430
+ stop() {
14431
+ this.recorders = this.recorders.filter((x2) => x2 !== recorder);
14432
+ return state;
14433
+ }
14434
+ };
14435
+ this.recorders.push(recorder);
14436
+ return recorder;
14258
14437
  }
14259
14438
  };
14260
14439
 
14261
- // ../core/src/Selection.js
14262
- function isSelection(x2) {
14263
- return x2 instanceof Selection;
14440
+ // ../core/src/util/js-type.js
14441
+ function jsType(type) {
14442
+ switch (type) {
14443
+ case "BIGINT":
14444
+ case "HUGEINT":
14445
+ case "INTEGER":
14446
+ case "SMALLINT":
14447
+ case "TINYINT":
14448
+ case "UBIGINT":
14449
+ case "UINTEGER":
14450
+ case "USMALLINT":
14451
+ case "UTINYINT":
14452
+ case "DOUBLE":
14453
+ case "FLOAT":
14454
+ case "REAL":
14455
+ return "number";
14456
+ case "DATE":
14457
+ case "TIMESTAMP":
14458
+ case "TIMESTAMPTZ":
14459
+ case "TIMESTAMP WITH TIME ZONE":
14460
+ case "TIME":
14461
+ case "TIMESTAMP_NS":
14462
+ return "date";
14463
+ case "BOOLEAN":
14464
+ return "boolean";
14465
+ case "VARCHAR":
14466
+ case "UUID":
14467
+ case "JSON":
14468
+ return "string";
14469
+ case "ARRAY":
14470
+ case "LIST":
14471
+ return "array";
14472
+ case "BLOB":
14473
+ case "STRUCT":
14474
+ case "MAP":
14475
+ case "GEOMETRY":
14476
+ return "object";
14477
+ default:
14478
+ if (type.startsWith("DECIMAL")) {
14479
+ return "number";
14480
+ } else if (type.startsWith("STRUCT") || type.startsWith("MAP")) {
14481
+ return "object";
14482
+ } else if (type.endsWith("]")) {
14483
+ return "array";
14484
+ }
14485
+ throw new Error(`Unsupported type: ${type}`);
14486
+ }
14264
14487
  }
14265
- var Selection = class _Selection extends Param {
14266
- /**
14267
- * Create a new Selection instance with an
14268
- * intersect (conjunction) resolution strategy.
14269
- * @param {object} [options] The selection options.
14270
- * @param {boolean} [options.cross=false] Boolean flag indicating
14271
- * cross-filtered resolution. If true, selection clauses will not
14272
- * be applied to the clients they are associated with.
14273
- * @returns {Selection} The new Selection instance.
14274
- */
14275
- static intersect({ cross = false } = {}) {
14276
- return new _Selection(new SelectionResolver({ cross }));
14488
+
14489
+ // ../core/src/util/convert-arrow.js
14490
+ function convertArrowValue(type) {
14491
+ if (DataType.isTimestamp(type)) {
14492
+ return (v) => v == null ? v : new Date(v);
14277
14493
  }
14278
- /**
14279
- * Create a new Selection instance with a
14280
- * union (disjunction) resolution strategy.
14281
- * @param {object} [options] The selection options.
14282
- * @param {boolean} [options.cross=false] Boolean flag indicating
14283
- * cross-filtered resolution. If true, selection clauses will not
14284
- * be applied to the clients they are associated with.
14285
- * @returns {Selection} The new Selection instance.
14286
- */
14287
- static union({ cross = false } = {}) {
14288
- return new _Selection(new SelectionResolver({ cross, union: true }));
14494
+ if (DataType.isInt(type) && type.bitWidth >= 64) {
14495
+ return (v) => v == null ? v : Number(v);
14289
14496
  }
14290
- /**
14291
- * Create a new Selection instance with a singular resolution strategy
14292
- * that keeps only the most recent selection clause.
14293
- * @param {object} [options] The selection options.
14294
- * @param {boolean} [options.cross=false] Boolean flag indicating
14295
- * cross-filtered resolution. If true, selection clauses will not
14296
- * be applied to the clients they are associated with.
14297
- * @returns {Selection} The new Selection instance.
14298
- */
14299
- static single({ cross = false } = {}) {
14300
- return new _Selection(new SelectionResolver({ cross, single: true }));
14497
+ if (DataType.isDecimal(type)) {
14498
+ const scale = 1 / Math.pow(10, type.scale);
14499
+ return (v) => v == null ? v : decimalToNumber(v, scale);
14301
14500
  }
14302
- /**
14303
- * Create a new Selection instance with a
14304
- * cross-filtered intersect resolution strategy.
14305
- * @returns {Selection} The new Selection instance.
14306
- */
14307
- static crossfilter() {
14308
- return new _Selection(new SelectionResolver({ cross: true }));
14501
+ return (v) => v;
14502
+ }
14503
+ var BASE32 = Array.from(
14504
+ { length: 8 },
14505
+ (_, i) => Math.pow(2, i * 32)
14506
+ );
14507
+ function decimalToNumber(v, scale) {
14508
+ const n = v.length;
14509
+ let x2 = 0;
14510
+ if (v.signed && (v[n - 1] | 0) < 0) {
14511
+ for (let i = 0; i < n; ++i) {
14512
+ x2 += ~v[i] * BASE32[i];
14513
+ }
14514
+ x2 = -(x2 + 1);
14515
+ } else {
14516
+ for (let i = 0; i < n; ++i) {
14517
+ x2 += v[i] * BASE32[i];
14518
+ }
14309
14519
  }
14310
- /**
14311
- * Create a new Selection instance.
14312
- * @param {SelectionResolver} resolver The selection resolution
14313
- * strategy to apply.
14314
- */
14315
- constructor(resolver = new SelectionResolver()) {
14316
- super([]);
14317
- this._resolved = this._value;
14318
- this._resolver = resolver;
14520
+ return x2 * scale;
14521
+ }
14522
+
14523
+ // ../core/src/util/field-info.js
14524
+ var Count = "count";
14525
+ var Nulls = "nulls";
14526
+ var Max = "max";
14527
+ var Min = "min";
14528
+ var Distinct = "distinct";
14529
+ var statMap = {
14530
+ [Count]: count,
14531
+ [Distinct]: (column2) => count(column2).distinct(),
14532
+ [Max]: max,
14533
+ [Min]: min,
14534
+ [Nulls]: (column2) => count().where(isNull(column2))
14535
+ };
14536
+ function summarize(table2, column2, stats) {
14537
+ return Query.from(table2).select(Array.from(stats, (s) => [s, statMap[s](column2)]));
14538
+ }
14539
+ async function queryFieldInfo(mc, fields) {
14540
+ if (fields.length === 1 && `${fields[0].column}` === "*") {
14541
+ return getTableInfo(mc, fields[0].table);
14542
+ } else {
14543
+ return (await Promise.all(fields.map((f) => getFieldInfo(mc, f)))).filter((x2) => x2);
14319
14544
  }
14320
- /**
14321
- * Create a cloned copy of this Selection instance.
14322
- * @returns {Selection} A clone of this selection.
14323
- */
14324
- clone() {
14325
- const s = new _Selection(this._resolver);
14326
- s._value = s._resolved = this._value;
14327
- return s;
14545
+ }
14546
+ async function getFieldInfo(mc, { table: table2, column: column2, stats }) {
14547
+ const q = Query.from({ source: table2 }).select({ column: column2 }).groupby(column2.aggregate ? sql`ALL` : []);
14548
+ const [desc2] = Array.from(await mc.query(Query.describe(q)));
14549
+ const info = {
14550
+ table: table2,
14551
+ column: `${column2}`,
14552
+ sqlType: desc2.column_type,
14553
+ type: jsType(desc2.column_type),
14554
+ nullable: desc2.null === "YES"
14555
+ };
14556
+ if (!(stats?.length || stats?.size)) return info;
14557
+ const result = await mc.query(
14558
+ summarize(table2, column2, stats),
14559
+ { persist: true }
14560
+ );
14561
+ for (let i = 0; i < result.numCols; ++i) {
14562
+ const { name } = result.schema.fields[i];
14563
+ const child = result.getChildAt(i);
14564
+ const convert = convertArrowValue(child.type);
14565
+ info[name] = convert(child.get(0));
14328
14566
  }
14329
- /**
14330
- * Create a clone of this Selection with clauses corresponding
14331
- * to the provided source removed.
14332
- * @param {*} source The clause source to remove.
14333
- * @returns {Selection} A cloned and updated Selection.
14334
- */
14335
- remove(source) {
14336
- const s = this.clone();
14337
- s._value = s._resolved = s._resolver.resolve(this._resolved, { source });
14338
- s._value.active = { source };
14339
- return s;
14567
+ return info;
14568
+ }
14569
+ async function getTableInfo(mc, table2) {
14570
+ const result = await mc.query(`DESCRIBE ${asRelation(table2)}`);
14571
+ return Array.from(result).map((desc2) => ({
14572
+ table: table2,
14573
+ column: desc2.column_name,
14574
+ sqlType: desc2.column_type,
14575
+ type: jsType(desc2.column_type),
14576
+ nullable: desc2.null === "YES"
14577
+ }));
14578
+ }
14579
+
14580
+ // ../core/src/util/void-logger.js
14581
+ function voidLogger() {
14582
+ return {
14583
+ debug() {
14584
+ },
14585
+ info() {
14586
+ },
14587
+ log() {
14588
+ },
14589
+ warn() {
14590
+ },
14591
+ error() {
14592
+ }
14593
+ };
14594
+ }
14595
+
14596
+ // ../core/src/Coordinator.js
14597
+ var _instance;
14598
+ function coordinator(instance8) {
14599
+ if (instance8) {
14600
+ _instance = instance8;
14601
+ } else if (_instance == null) {
14602
+ _instance = new Coordinator();
14340
14603
  }
14341
- /**
14342
- * The current active (most recently updated) selection clause.
14343
- */
14344
- get active() {
14345
- return this.clauses.active;
14604
+ return _instance;
14605
+ }
14606
+ var Coordinator = class {
14607
+ constructor(db = socketConnector(), options = {}) {
14608
+ const {
14609
+ logger = console,
14610
+ manager = new QueryManager()
14611
+ } = options;
14612
+ this.manager = manager;
14613
+ this.logger(logger);
14614
+ this.configure(options);
14615
+ this.databaseConnector(db);
14616
+ this.clear();
14346
14617
  }
14347
- /**
14348
- * The value corresponding to the current active selection clause.
14349
- * This method ensures compatibility where a normal Param is expected.
14350
- */
14351
- get value() {
14352
- return this.active?.value;
14618
+ logger(logger) {
14619
+ if (arguments.length) {
14620
+ this._logger = logger || voidLogger();
14621
+ this.manager.logger(this._logger);
14622
+ }
14623
+ return this._logger;
14353
14624
  }
14354
14625
  /**
14355
- * The current array of selection clauses.
14626
+ * Set configuration options for this coordinator.
14627
+ * @param {object} [options] Configration options.
14628
+ * @param {boolean} [options.cache=true] Boolean flag to enable/disable query caching.
14629
+ * @param {boolean} [options.consolidate=true] Boolean flag to enable/disable query consolidation.
14630
+ * @param {boolean|object} [options.indexes=true] Boolean flag to enable/disable
14631
+ * automatic data cube indexes or an index options object.
14356
14632
  */
14357
- get clauses() {
14358
- return super.value;
14633
+ configure({ cache = true, consolidate: consolidate2 = true, indexes = true } = {}) {
14634
+ this.manager.cache(cache);
14635
+ this.manager.consolidate(consolidate2);
14636
+ this.indexes = indexes;
14359
14637
  }
14360
- /**
14361
- * Indicate if this selection has a single resolution strategy.
14362
- */
14363
- get single() {
14364
- return this._resolver.single;
14638
+ clear({ clients = true, cache = true } = {}) {
14639
+ this.manager.clear();
14640
+ if (clients) {
14641
+ this.clients?.forEach((client) => this.disconnect(client));
14642
+ this.filterGroups?.forEach((group) => group.finalize());
14643
+ this.clients = /* @__PURE__ */ new Set();
14644
+ this.filterGroups = /* @__PURE__ */ new Map();
14645
+ }
14646
+ if (cache) this.manager.cache().clear();
14365
14647
  }
14366
- /**
14367
- * Emit an activate event with the given selection clause.
14368
- * @param {*} clause The clause repesenting the potential activation.
14369
- */
14370
- activate(clause) {
14371
- this.emit("activate", clause);
14648
+ databaseConnector(db) {
14649
+ return this.manager.connector(db);
14372
14650
  }
14373
- /**
14374
- * Update the selection with a new selection clause.
14375
- * @param {*} clause The selection clause to add.
14376
- * @returns {this} This Selection instance.
14377
- */
14378
- update(clause) {
14379
- this._resolved = this._resolver.resolve(this._resolved, clause, true);
14380
- this._resolved.active = clause;
14381
- return super.update(this._resolved);
14651
+ // -- Query Management ----
14652
+ cancel(requests) {
14653
+ this.manager.cancel(requests);
14382
14654
  }
14383
- /**
14384
- * Upon value-typed updates, sets the current clause list to the
14385
- * input value and returns the active clause value.
14386
- * @param {string} type The event type.
14387
- * @param {*} value The input event value.
14388
- * @returns {*} For value-typed events, returns the active clause
14389
- * values. Otherwise returns the input event value as-is.
14390
- */
14391
- willEmit(type, value) {
14392
- if (type === "value") {
14393
- this._value = value;
14394
- return this.value;
14395
- }
14396
- return value;
14655
+ exec(query, { priority = Priority.Normal } = {}) {
14656
+ query = Array.isArray(query) ? query.join(";\n") : query;
14657
+ return this.manager.request({ type: "exec", query }, priority);
14397
14658
  }
14398
- /**
14399
- * Upon value-typed updates, returns a dispatch queue filter function.
14400
- * The return value depends on the selection resolution strategy.
14401
- * @param {string} type The event type.
14402
- * @param {*} value The new event value that will be enqueued.
14403
- * @returns {(value: *) => boolean|null} For value-typed events,
14404
- * returns a dispatch queue filter function. Otherwise returns null.
14405
- */
14406
- emitQueueFilter(type, value) {
14407
- return type === "value" ? this._resolver.queueFilter(value) : null;
14659
+ query(query, {
14660
+ type = "arrow",
14661
+ cache = true,
14662
+ priority = Priority.Normal,
14663
+ ...options
14664
+ } = {}) {
14665
+ return this.manager.request({ type, query, cache, options }, priority);
14408
14666
  }
14409
- /**
14410
- * Indicates if a selection clause should not be applied to a given client.
14411
- * The return value depends on the selection resolution strategy.
14412
- * @param {*} client The selection clause.
14413
- * @param {*} clause The client to test.
14414
- * @returns True if the client should be skipped, false otherwise.
14415
- */
14416
- skip(client, clause) {
14417
- return this._resolver.skip(client, clause);
14667
+ prefetch(query, options = {}) {
14668
+ return this.query(query, { ...options, cache: true, priority: Priority.Low });
14418
14669
  }
14419
- /**
14420
- * Return a selection query predicate for the given client.
14421
- * @param {*} client The client whose data may be filtered.
14422
- * @param {boolean} [noSkip=false] Disable skipping of active
14423
- * cross-filtered sources. If set true, the source of the active
14424
- * clause in a cross-filtered selection will not be skipped.
14425
- * @returns {*} The query predicate for filtering client data,
14426
- * based on the current state of this selection.
14427
- */
14428
- predicate(client, noSkip = false) {
14429
- const { clauses } = this;
14430
- const active = noSkip ? null : clauses.active;
14431
- return this._resolver.predicate(clauses, active, client);
14670
+ createBundle(name, queries, priority = Priority.Low) {
14671
+ const options = { name, queries };
14672
+ return this.manager.request({ type: "create-bundle", options }, priority);
14432
14673
  }
14433
- };
14434
- var SelectionResolver = class {
14435
- /**
14436
- * Create a new selection resolved instance.
14437
- * @param {object} [options] The resolution strategy options.
14438
- * @param {boolean} [options.union=false] Boolean flag to indicate a union strategy.
14439
- * If false, an intersection strategy is used.
14440
- * @param {boolean} [options.cross=false] Boolean flag to indicate cross-filtering.
14441
- * @param {boolean} [options.single=false] Boolean flag to indicate single clauses only.
14442
- */
14443
- constructor({ union, cross, single } = {}) {
14444
- this.union = !!union;
14445
- this.cross = !!cross;
14446
- this.single = !!single;
14674
+ loadBundle(name, priority = Priority.High) {
14675
+ const options = { name };
14676
+ return this.manager.request({ type: "load-bundle", options }, priority);
14447
14677
  }
14448
- /**
14449
- * Resolve a list of selection clauses according to the resolution strategy.
14450
- * @param {*[]} clauseList An array of selection clauses.
14451
- * @param {*} clause A new selection clause to add.
14452
- * @returns {*[]} An updated array of selection clauses.
14453
- */
14454
- resolve(clauseList, clause, reset = false) {
14455
- const { source, predicate } = clause;
14456
- const filtered = clauseList.filter((c) => source !== c.source);
14457
- const clauses = this.single ? [] : filtered;
14458
- if (this.single && reset) filtered.forEach((c) => c.source?.reset?.());
14459
- if (predicate) clauses.push(clause);
14460
- return clauses;
14678
+ // -- Client Management ----
14679
+ updateClient(client, query, priority = Priority.Normal) {
14680
+ client.queryPending();
14681
+ return this.query(query, { priority }).then(
14682
+ (data) => client.queryResult(data).update(),
14683
+ (err) => {
14684
+ client.queryError(err);
14685
+ this._logger.error(err);
14686
+ }
14687
+ );
14461
14688
  }
14462
- /**
14463
- * Indicates if a selection clause should not be applied to a given client.
14464
- * The return value depends on the resolution strategy.
14465
- * @param {*} client The selection clause.
14466
- * @param {*} clause The client to test.
14467
- * @returns True if the client should be skipped, false otherwise.
14468
- */
14469
- skip(client, clause) {
14470
- return this.cross && clause?.clients?.has(client);
14689
+ requestQuery(client, query) {
14690
+ this.filterGroups.get(client.filterBy)?.reset();
14691
+ return query ? this.updateClient(client, query) : client.update();
14471
14692
  }
14472
14693
  /**
14473
- * Return a selection query predicate for the given client.
14474
- * @param {*[]} clauseList An array of selection clauses.
14475
- * @param {*} active The current active selection clause.
14476
- * @param {*} client The client whose data may be filtered.
14477
- * @returns {*} The query predicate for filtering client data,
14478
- * based on the current state of this selection.
14694
+ * Connect a client to the coordinator.
14695
+ * @param {import('./MosaicClient.js').MosaicClient} client the client to disconnect
14479
14696
  */
14480
- predicate(clauseList, active, client) {
14481
- const { union } = this;
14482
- if (this.skip(client, active)) return void 0;
14483
- const predicates = clauseList.filter((clause) => !this.skip(client, clause)).map((clause) => clause.predicate);
14484
- return union && predicates.length > 1 ? or(predicates) : predicates;
14697
+ async connect(client) {
14698
+ const { clients, filterGroups, indexes } = this;
14699
+ if (clients.has(client)) {
14700
+ throw new Error("Client already connected.");
14701
+ }
14702
+ clients.add(client);
14703
+ client.coordinator = this;
14704
+ const fields = client.fields();
14705
+ if (fields?.length) {
14706
+ client.fieldInfo(await queryFieldInfo(this, fields));
14707
+ }
14708
+ const filter = client.filterBy;
14709
+ if (filter) {
14710
+ if (filterGroups.has(filter)) {
14711
+ filterGroups.get(filter).add(client);
14712
+ } else {
14713
+ const group = new FilterGroup(this, filter, indexes);
14714
+ filterGroups.set(filter, group.add(client));
14715
+ }
14716
+ }
14717
+ client.requestQuery();
14485
14718
  }
14486
14719
  /**
14487
- * Returns a filter function for queued selection updates.
14488
- * @param {*} value The new event value that will be enqueued.
14489
- * @returns {(value: *) => boolean|null} A dispatch queue filter
14490
- * function, or null if all unemitted event values should be filtered.
14720
+ * Disconnect a client from the coordinator.
14721
+ *
14722
+ * @param {import('./MosaicClient.js').MosaicClient} client the client to disconnect
14491
14723
  */
14492
- queueFilter(value) {
14493
- if (this.cross) {
14494
- const source = value.active?.source;
14495
- return (clauses) => clauses.active?.source !== source;
14496
- }
14497
- return null;
14724
+ disconnect(client) {
14725
+ const { clients, filterGroups } = this;
14726
+ if (!clients.has(client)) return;
14727
+ clients.delete(client);
14728
+ filterGroups.get(client.filterBy)?.remove(client);
14729
+ client.coordinator = null;
14498
14730
  }
14499
14731
  };
14500
14732
 
14733
+ // ../core/src/SelectionClause.js
14734
+ function point(field, value, { source, clients = void 0 }) {
14735
+ const predicate = value !== void 0 ? isNotDistinct(field, literal(value)) : null;
14736
+ return {
14737
+ meta: { type: "point" },
14738
+ source,
14739
+ clients,
14740
+ value,
14741
+ predicate
14742
+ };
14743
+ }
14744
+ function interval(field, value, {
14745
+ source,
14746
+ clients,
14747
+ bin,
14748
+ scale,
14749
+ pixelSize = 1
14750
+ }) {
14751
+ const predicate = value != null ? isBetween(field, value) : null;
14752
+ const meta = { type: "interval", scales: [scale], bin, pixelSize };
14753
+ return { meta, source, clients, value, predicate };
14754
+ }
14755
+ var MATCH_METHODS = { contains, prefix, suffix, regexp: regexp_matches };
14756
+ function match(field, value, {
14757
+ source,
14758
+ clients = void 0,
14759
+ method = "contains"
14760
+ }) {
14761
+ let fn = MATCH_METHODS[method];
14762
+ const predicate = value ? fn(field, literal(value)) : null;
14763
+ const meta = { type: "match", method };
14764
+ return { meta, source, clients, value, predicate };
14765
+ }
14766
+
14501
14767
  // src/input.js
14502
14768
  function input(InputClass, options) {
14503
14769
  const input2 = new InputClass(options);
@@ -14512,8 +14778,31 @@ var isObject2 = (v) => {
14512
14778
  var menu = (options) => input(Menu, options);
14513
14779
  var Menu = class extends MosaicClient {
14514
14780
  /**
14515
- * Create a new Menu instance.
14516
- * @param {object} options Options object
14781
+ * Create a new menu input.
14782
+ * @param {object} [options] Options object
14783
+ * @param {HTMLElement} [options.element] The parent DOM element in which to
14784
+ * place the menu elements. If undefined, a new `div` element is created.
14785
+ * @param {Selection} [options.filterBy] A selection to filter the database
14786
+ * table indicated by the *from* option.
14787
+ * @param {Param} [options.as] The output param or selection. A selection
14788
+ * clause is added for the currently selected menu option.
14789
+ * @param {string} [options.field] The database column name to use within
14790
+ * generated selection clause predicates. Defaults to the *column* option.
14791
+ * @param {(any | { value: any, label?: string })[]} [options.options] An
14792
+ * array of menu options, as literal values or option objects. Option
14793
+ * objects have a `value` property and an optional `label` property. If no
14794
+ * label or *format* function is provided, the string-coerced value is used.
14795
+ * @param {(value: any) => string} [options.format] A format function that
14796
+ * takes an option value as input and generates a string label. The format
14797
+ * function is not applied when an explicit label is provided in an option
14798
+ * object.
14799
+ * @param {*} [options.value] The initial selected menu value.
14800
+ * @param {string} [options.from] The name of a database table to use as a data
14801
+ * source for this widget. Used in conjunction with the *column* option.
14802
+ * @param {string} [options.column] The name of a database column from which
14803
+ * to pull menu options. The unique column values are used as menu options.
14804
+ * Used in conjunction with the *from* option.
14805
+ * @param {string} [options.label] A text label for this input.
14517
14806
  */
14518
14807
  constructor({
14519
14808
  element,
@@ -14525,32 +14814,37 @@ var Menu = class extends MosaicClient {
14525
14814
  // TODO
14526
14815
  options,
14527
14816
  value,
14817
+ field = column2,
14528
14818
  as
14529
14819
  } = {}) {
14530
14820
  super(filterBy);
14531
14821
  this.from = from;
14532
14822
  this.column = column2;
14533
- this.selection = as;
14534
14823
  this.format = format2;
14824
+ this.field = field;
14825
+ const selection = this.selection = as;
14535
14826
  this.element = element ?? document.createElement("div");
14536
14827
  this.element.setAttribute("class", "input");
14537
- this.element.value = this;
14828
+ Object.defineProperty(this.element, "value", { value: this });
14538
14829
  const lab = document.createElement("label");
14539
14830
  lab.innerText = label || column2;
14540
14831
  this.element.appendChild(lab);
14541
14832
  this.select = document.createElement("select");
14833
+ this.element.appendChild(this.select);
14542
14834
  if (options) {
14543
14835
  this.data = options.map((value2) => isObject2(value2) ? value2 : { value: value2 });
14836
+ this.selectedValue(value ?? "");
14544
14837
  this.update();
14545
14838
  }
14546
- value = value ?? this.selection?.value ?? this.data?.[0]?.value;
14547
- if (this.selection?.value === void 0) this.publish(value);
14548
- this.element.appendChild(this.select);
14549
- if (this.selection) {
14839
+ if (selection) {
14840
+ const isParam2 = !isSelection(selection);
14841
+ if (value != null && (!isParam2 || selection.value === void 0)) {
14842
+ this.publish(value);
14843
+ }
14550
14844
  this.select.addEventListener("input", () => {
14551
14845
  this.publish(this.selectedValue() ?? null);
14552
14846
  });
14553
- if (!isSelection(this.selection)) {
14847
+ if (isParam2) {
14554
14848
  this.selection.addEventListener("value", (value2) => {
14555
14849
  if (value2 !== this.select.value) {
14556
14850
  this.selectedValue(value2);
@@ -14576,14 +14870,11 @@ var Menu = class extends MosaicClient {
14576
14870
  this.select.selectedIndex = this.from ? 0 : -1;
14577
14871
  }
14578
14872
  publish(value) {
14579
- const { selection, column: column2 } = this;
14873
+ const { selection, field } = this;
14580
14874
  if (isSelection(selection)) {
14581
- selection.update({
14582
- source: this,
14583
- schema: { type: "point" },
14584
- value,
14585
- predicate: value !== "" && value !== void 0 ? eq(column2, literal(value)) : null
14586
- });
14875
+ if (value === "") value = void 0;
14876
+ const clause = point(field, value, { source: this });
14877
+ selection.update(clause);
14587
14878
  } else if (isParam(selection)) {
14588
14879
  selection.update(value);
14589
14880
  }
@@ -14598,7 +14889,7 @@ var Menu = class extends MosaicClient {
14598
14889
  return this;
14599
14890
  }
14600
14891
  update() {
14601
- const { data, format: format2, select } = this;
14892
+ const { data, format: format2, select, selection } = this;
14602
14893
  select.replaceChildren();
14603
14894
  for (const { value, label } of data) {
14604
14895
  const opt = document.createElement("option");
@@ -14606,21 +14897,42 @@ var Menu = class extends MosaicClient {
14606
14897
  opt.innerText = label ?? format2(value);
14607
14898
  this.select.appendChild(opt);
14608
14899
  }
14609
- if (this.selection) {
14610
- this.selectedValue(this.selection?.value ?? "");
14900
+ if (selection) {
14901
+ const value = isSelection(selection) ? selection.valueFor(this) : selection.value;
14902
+ this.selectedValue(value ?? "");
14611
14903
  }
14612
14904
  return this;
14613
14905
  }
14614
14906
  };
14615
14907
 
14616
14908
  // src/Search.js
14617
- var FUNCTIONS = { contains, prefix, suffix, regexp: regexp_matches };
14618
14909
  var _id = 0;
14619
14910
  var search = (options) => input(Search, options);
14620
14911
  var Search = class extends MosaicClient {
14621
14912
  /**
14622
- * Create a new Search instance.
14623
- * @param {object} options Options object
14913
+ * Create a new text search input.
14914
+ * @param {object} [options] Options object
14915
+ * @param {HTMLElement} [options.element] The parent DOM element in which to
14916
+ * place the search elements. If undefined, a new `div` element is created.
14917
+ * @param {Selection} [options.filterBy] A selection to filter the database
14918
+ * table indicated by the *from* option.
14919
+ * @param {Param} [options.as] The output param or selection. A selection
14920
+ * clause is added based on the current text search query.
14921
+ * @param {string} [options.field] The database column name to use within
14922
+ * generated selection clause predicates. Defaults to the *column* option.
14923
+ * @param {'contains' | 'prefix' | 'suffix' | 'regexp'} [options.type] The
14924
+ * type of text search query to perform. One of:
14925
+ * - `"contains"` (default): the query string may appear anywhere in the text
14926
+ * - `"prefix"`: the query string must appear at the start of the text
14927
+ * - `"suffix"`: the query string must appear at the end of the text
14928
+ * - `"regexp"`: the query string is a regular expression the text must match
14929
+ * @param {string} [options.from] The name of a database table to use as an
14930
+ * autocomplete data source for this widget. Used in conjunction with the
14931
+ * *column* option.
14932
+ * @param {string} [options.column] The name of a database column from which
14933
+ * to pull valid search results. The unique column values are used as search
14934
+ * autocomplete values. Used in conjunction with the *from* option.
14935
+ * @param {string} [options.label] A text label for this input.
14624
14936
  */
14625
14937
  constructor({
14626
14938
  element,
@@ -14629,6 +14941,7 @@ var Search = class extends MosaicClient {
14629
14941
  column: column2,
14630
14942
  label,
14631
14943
  type = "contains",
14944
+ field = column2,
14632
14945
  as
14633
14946
  } = {}) {
14634
14947
  super(filterBy);
@@ -14637,9 +14950,10 @@ var Search = class extends MosaicClient {
14637
14950
  this.from = from;
14638
14951
  this.column = column2;
14639
14952
  this.selection = as;
14953
+ this.field = field;
14640
14954
  this.element = element ?? document.createElement("div");
14641
14955
  this.element.setAttribute("class", "input");
14642
- this.element.value = this;
14956
+ Object.defineProperty(this.element, "value", { value: this });
14643
14957
  if (label) {
14644
14958
  const lab = document.createElement("label");
14645
14959
  lab.setAttribute("for", this.id);
@@ -14668,14 +14982,10 @@ var Search = class extends MosaicClient {
14668
14982
  this.searchbox.value = "";
14669
14983
  }
14670
14984
  publish(value) {
14671
- const { selection, column: column2, type } = this;
14985
+ const { selection, field, type } = this;
14672
14986
  if (isSelection(selection)) {
14673
- selection.update({
14674
- source: this,
14675
- schema: { type },
14676
- value,
14677
- predicate: value ? FUNCTIONS[type](column2, literal(value)) : null
14678
- });
14987
+ const clause = match(field, value, { source: this, method: type });
14988
+ selection.update(clause);
14679
14989
  } else if (isParam(selection)) {
14680
14990
  selection.update(value);
14681
14991
  }
@@ -14710,8 +15020,34 @@ var _id2 = 0;
14710
15020
  var slider = (options) => input(Slider, options);
14711
15021
  var Slider = class extends MosaicClient {
14712
15022
  /**
14713
- * Create a new Slider instance.
14714
- * @param {object} options Options object
15023
+ * Create a new slider input.
15024
+ * @param {object} [options] Options object
15025
+ * @param {HTMLElement} [options.element] The parent DOM element in which to
15026
+ * place the slider elements. If undefined, a new `div` element is created.
15027
+ * @param {Selection} [options.filterBy] A selection to filter the database
15028
+ * table indicated by the *from* option.
15029
+ * @param {Param} [options.as] The output param or selection. A selection
15030
+ * clause is added based on the currently selected slider option.
15031
+ * @param {string} [options.field] The database column name to use within
15032
+ * generated selection clause predicates. Defaults to the *column* option.
15033
+ * @param {'point' | 'interval'} [options.select] The type of selection clause
15034
+ * predicate to generate if the **as** option is a Selection. If `'point'`
15035
+ * (the default), the selection predicate is an equality check for the slider
15036
+ * value. If `'interval'`, the predicate checks an interval from the minimum
15037
+ * to the current slider value.
15038
+ * @param {number} [options.min] The minimum slider value.
15039
+ * @param {number} [options.max] The maximum slider value.
15040
+ * @param {number} [options.step] The slider step, the amount to increment
15041
+ * between consecutive values.
15042
+ * @param {number} [options.value] The initial slider value.
15043
+ * @param {string} [options.from] The name of a database table to use as a data
15044
+ * source for this widget. Used in conjunction with the *column* option.
15045
+ * The minimum and maximum values of the column determine the slider range.
15046
+ * @param {string} [options.column] The name of a database column whose values
15047
+ * determine the slider range. Used in conjunction with the *from* option.
15048
+ * The minimum and maximum values of the column determine the slider range.
15049
+ * @param {string} [options.label] A text label for this input.
15050
+ * @param {number} [options.width] The width of the slider in screen pixels.
14715
15051
  */
14716
15052
  constructor({
14717
15053
  element,
@@ -14724,6 +15060,8 @@ var Slider = class extends MosaicClient {
14724
15060
  column: column2,
14725
15061
  label = column2,
14726
15062
  value = as?.value,
15063
+ select = "point",
15064
+ field = column2,
14727
15065
  width
14728
15066
  } = {}) {
14729
15067
  super(filterBy);
@@ -14731,41 +15069,49 @@ var Slider = class extends MosaicClient {
14731
15069
  this.from = from;
14732
15070
  this.column = column2 || "value";
14733
15071
  this.selection = as;
15072
+ this.selectionType = select;
15073
+ this.field = field;
14734
15074
  this.min = min2;
14735
15075
  this.max = max2;
14736
15076
  this.step = step;
14737
15077
  this.element = element || document.createElement("div");
14738
15078
  this.element.setAttribute("class", "input");
14739
- this.element.value = this;
15079
+ Object.defineProperty(this.element, "value", { value: this });
14740
15080
  if (label) {
14741
- const lab = document.createElement("label");
14742
- lab.setAttribute("for", this.id);
14743
- lab.innerText = label;
14744
- this.element.appendChild(lab);
15081
+ const desc2 = document.createElement("label");
15082
+ desc2.setAttribute("for", this.id);
15083
+ desc2.innerText = label;
15084
+ this.element.appendChild(desc2);
14745
15085
  }
14746
15086
  this.slider = document.createElement("input");
14747
15087
  this.slider.setAttribute("id", this.id);
14748
15088
  this.slider.setAttribute("type", "range");
14749
15089
  if (width != null) this.slider.style.width = `${+width}px`;
14750
- if (min2 != null) this.slider.setAttribute("min", min2);
14751
- if (max2 != null) this.slider.setAttribute("max", max2);
14752
- if (step != null) this.slider.setAttribute("step", step);
15090
+ if (min2 != null) this.slider.setAttribute("min", `${min2}`);
15091
+ if (max2 != null) this.slider.setAttribute("max", `${max2}`);
15092
+ if (step != null) this.slider.setAttribute("step", `${step}`);
15093
+ this.element.appendChild(this.slider);
15094
+ this.curval = document.createElement("label");
15095
+ this.curval.setAttribute("for", this.id);
15096
+ this.curval.setAttribute("class", "value");
15097
+ this.element.appendChild(this.curval);
14753
15098
  if (value != null) {
14754
- this.slider.setAttribute("value", value);
15099
+ this.slider.setAttribute("value", `${value}`);
14755
15100
  if (this.selection?.value === void 0) this.publish(value);
14756
15101
  }
14757
- this.element.appendChild(this.slider);
14758
- if (this.selection) {
14759
- this.slider.addEventListener("input", () => {
14760
- this.publish(+this.slider.value);
15102
+ this.curval.innerText = this.slider.value;
15103
+ this.slider.addEventListener("input", () => {
15104
+ const { value: value2 } = this.slider;
15105
+ this.curval.innerText = value2;
15106
+ if (this.selection) this.publish(+value2);
15107
+ });
15108
+ if (this.selection && !isSelection(this.selection)) {
15109
+ this.selection.addEventListener("value", (value2) => {
15110
+ if (value2 !== +this.slider.value) {
15111
+ this.slider.value = value2;
15112
+ this.curval.innerText = value2;
15113
+ }
14761
15114
  });
14762
- if (!isSelection(this.selection)) {
14763
- this.selection.addEventListener("value", (value2) => {
14764
- if (value2 !== +this.slider.value) {
14765
- this.slider.value = value2;
14766
- }
14767
- });
14768
- }
14769
15115
  }
14770
15116
  }
14771
15117
  query(filter = []) {
@@ -14775,20 +15121,34 @@ var Slider = class extends MosaicClient {
14775
15121
  }
14776
15122
  queryResult(data) {
14777
15123
  const { min: min2, max: max2 } = Array.from(data)[0];
14778
- if (this.min == null) this.slider.setAttribute("min", min2);
14779
- if (this.max == null) this.slider.setAttribute("max", max2);
14780
- if (this.step == null) this.slider.setAttribute("step", String((max2 - min2) / 500));
15124
+ if (this.min == null) {
15125
+ this.min = min2;
15126
+ this.slider.setAttribute("min", `${min2}`);
15127
+ }
15128
+ if (this.max == null) {
15129
+ this.max = max2;
15130
+ this.slider.setAttribute("max", `${max2}`);
15131
+ }
15132
+ if (this.step == null) {
15133
+ this.step = (max2 - min2) / 500;
15134
+ this.slider.setAttribute("step", `${this.step}`);
15135
+ }
14781
15136
  return this;
14782
15137
  }
14783
15138
  publish(value) {
14784
- const { selection, column: column2 } = this;
15139
+ const { field, selectionType, selection } = this;
14785
15140
  if (isSelection(selection)) {
14786
- selection.update({
14787
- source: this,
14788
- schema: { type: "point" },
14789
- value,
14790
- predicate: eq(column2, literal(value))
14791
- });
15141
+ if (selectionType === "interval") {
15142
+ const domain = [this.min ?? 0, value];
15143
+ selection.update(interval(field, domain, {
15144
+ source: this,
15145
+ bin: "ceil",
15146
+ scale: { type: "identity", domain },
15147
+ pixelSize: this.step
15148
+ }));
15149
+ } else {
15150
+ selection.update(point(field, value, { source: this }));
15151
+ }
14792
15152
  } else if (isParam(this.selection)) {
14793
15153
  selection.update(value);
14794
15154
  }
@@ -14866,7 +15226,7 @@ var Table2 = class extends MosaicClient {
14866
15226
  this.sortDesc = false;
14867
15227
  this.element = element || document.createElement("div");
14868
15228
  this.element.setAttribute("id", this.id);
14869
- this.element.value = this;
15229
+ Object.defineProperty(this.element, "value", { value: this });
14870
15230
  if (typeof width === "number") this.element.style.width = `${width}px`;
14871
15231
  if (maxWidth) this.element.style.maxWidth = `${maxWidth}px`;
14872
15232
  this.element.style.maxHeight = `${height}px`;