@uwdata/mosaic-core 0.0.1 → 0.1.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.
@@ -4,41 +4,6 @@ var __export = (target, all2) => {
4
4
  __defProp(target, name, { get: all2[name], enumerable: true });
5
5
  };
6
6
 
7
- // src/MosaicClient.js
8
- var MosaicClient = class {
9
- constructor(filterSelection) {
10
- this._filterBy = filterSelection;
11
- }
12
- get filterBy() {
13
- return this._filterBy;
14
- }
15
- get filterIndexable() {
16
- return true;
17
- }
18
- fields() {
19
- return null;
20
- }
21
- fieldStats() {
22
- return this;
23
- }
24
- query() {
25
- return null;
26
- }
27
- queryPending() {
28
- return this;
29
- }
30
- queryResult() {
31
- return this;
32
- }
33
- queryError(error) {
34
- console.error(error);
35
- return this;
36
- }
37
- update() {
38
- return this;
39
- }
40
- };
41
-
42
7
  // ../../node_modules/tslib/tslib.es6.js
43
8
  function __rest(s, e) {
44
9
  var t = {};
@@ -11532,6 +11497,44 @@ function socketClient(uri = "ws://localhost:3000/") {
11532
11497
  };
11533
11498
  }
11534
11499
 
11500
+ // src/util/js-type.js
11501
+ function jsType(type) {
11502
+ switch (type) {
11503
+ case "BIGINT":
11504
+ case "HUGEINT":
11505
+ case "INTEGER":
11506
+ case "SMALLINT":
11507
+ case "TINYINT":
11508
+ case "UBIGINT":
11509
+ case "UINTEGER":
11510
+ case "USMALLINT":
11511
+ case "UTINYINT":
11512
+ case "DOUBLE":
11513
+ case "FLOAT":
11514
+ case "REAL":
11515
+ case "DECIMAL":
11516
+ return "number";
11517
+ case "DATE":
11518
+ case "TIMESTAMP":
11519
+ case "TIMESTAMPTZ":
11520
+ case "TIME":
11521
+ return "date";
11522
+ case "BOOLEAN":
11523
+ return "boolean";
11524
+ case "VARCHAR":
11525
+ case "UUID":
11526
+ return "string";
11527
+ case "LIST":
11528
+ return "array";
11529
+ case "BLOB":
11530
+ case "STRUCT":
11531
+ case "MAP":
11532
+ return "object";
11533
+ default:
11534
+ throw new Error(`Unsupported type: ${type}`);
11535
+ }
11536
+ }
11537
+
11535
11538
  // ../sql/src/ref.js
11536
11539
  var Ref = class {
11537
11540
  constructor(table, column2) {
@@ -11568,23 +11571,6 @@ function relation(name) {
11568
11571
  function column(table, column2) {
11569
11572
  return arguments.length === 1 ? new Ref(null, table) : new Ref(table, column2);
11570
11573
  }
11571
- function expr(sql, columns, label) {
11572
- return {
11573
- expr: sql,
11574
- label,
11575
- toString: () => `${sql}`,
11576
- get columns() {
11577
- return columns || [];
11578
- }
11579
- };
11580
- }
11581
- function transform(func2, label) {
11582
- return (value) => expr(
11583
- func2(value),
11584
- asColumn(value).columns,
11585
- label
11586
- );
11587
- }
11588
11574
 
11589
11575
  // ../sql/src/to-sql.js
11590
11576
  function toSQL(value) {
@@ -11609,6 +11595,27 @@ function literalToSQL(value) {
11609
11595
  }
11610
11596
  }
11611
11597
 
11598
+ // ../sql/src/expression.js
11599
+ function isExpression(e) {
11600
+ return e instanceof SQLExpression;
11601
+ }
11602
+ var SQLExpression = class {
11603
+ constructor(sql2, columns, label) {
11604
+ this.expr = sql2;
11605
+ this.label = label;
11606
+ this.columns = columns || [];
11607
+ }
11608
+ toString() {
11609
+ return `${this.expr}`;
11610
+ }
11611
+ };
11612
+ function expr(sql2, columns, label) {
11613
+ return new SQLExpression(sql2, columns, label);
11614
+ }
11615
+ function transform(func2, label) {
11616
+ return (value) => expr(func2(value), asColumn(value).columns, label);
11617
+ }
11618
+
11612
11619
  // ../sql/src/compare.js
11613
11620
  function extractColumns(...args) {
11614
11621
  return args.flat().flatMap((arg) => arg?.columns || []);
@@ -11769,10 +11776,10 @@ var Aggregate = class {
11769
11776
  toString() {
11770
11777
  const { aggregate, args, isDistinct: isDistinct2, filter } = this;
11771
11778
  const arg = args.length === 0 ? "*" : args.map(toSQL).join(", ");
11772
- const distinct = isDistinct2 ? "DISTINCT " : "";
11779
+ const distinct2 = isDistinct2 ? "DISTINCT " : "";
11773
11780
  const where = filter ? ` FILTER (WHERE ${toSQL(filter)})` : "";
11774
11781
  const cast = aggregate === "COUNT" ? "::INTEGER" : "";
11775
- return where && cast ? `(${aggregate}(${distinct}${arg})${where})${cast}` : `${aggregate}(${distinct}${arg})${where}${cast}`;
11782
+ return where && cast ? `(${aggregate}(${distinct2}${arg})${where})${cast}` : `${aggregate}(${distinct2}${arg})${where}${cast}`;
11776
11783
  }
11777
11784
  };
11778
11785
  function agg(op) {
@@ -11929,7 +11936,7 @@ var Query = class {
11929
11936
  list.push({ as: e, from: asRelation(e) });
11930
11937
  } else if (e instanceof Ref) {
11931
11938
  list.push({ as: e.table, from: e });
11932
- } else if (isQuery(e) || Object.hasOwn(e, "toString")) {
11939
+ } else if (isQuery(e) || isExpression(e)) {
11933
11940
  list.push({ from: e });
11934
11941
  } else if (Array.isArray(e)) {
11935
11942
  list.push({ as: unquote(e[0]), from: asRelation(e[1]) });
@@ -12073,7 +12080,7 @@ var Query = class {
12073
12080
  toString() {
12074
12081
  const {
12075
12082
  select,
12076
- distinct,
12083
+ distinct: distinct2,
12077
12084
  from,
12078
12085
  sample,
12079
12086
  where,
@@ -12086,60 +12093,60 @@ var Query = class {
12086
12093
  offset,
12087
12094
  with: cte
12088
12095
  } = this.query;
12089
- const sql = [];
12096
+ const sql2 = [];
12090
12097
  if (cte.length) {
12091
12098
  const list = cte.map(({ as, query }) => `"${as}" AS (${query})`);
12092
- sql.push(`WITH ${list.join(", ")}`);
12099
+ sql2.push(`WITH ${list.join(", ")}`);
12093
12100
  }
12094
12101
  const sels = select.map(
12095
12102
  ({ as, expr: expr2 }) => isColumnRefFor(expr2, as) && !expr2.table ? `${expr2}` : `${expr2} AS "${as}"`
12096
12103
  );
12097
- sql.push(`SELECT${distinct ? " DISTINCT" : ""} ${sels.join(", ")}`);
12104
+ sql2.push(`SELECT${distinct2 ? " DISTINCT" : ""} ${sels.join(", ")}`);
12098
12105
  if (from.length) {
12099
12106
  const rels = from.map(({ as, from: from2 }) => {
12100
12107
  const rel = isQuery(from2) ? `(${from2})` : `${from2}`;
12101
12108
  return !as || as === from2.table ? rel : `${rel} AS "${as}"`;
12102
12109
  });
12103
- sql.push(`FROM ${rels.join(", ")}`);
12110
+ sql2.push(`FROM ${rels.join(", ")}`);
12104
12111
  }
12105
12112
  if (sample) {
12106
12113
  const { rows, perc, method, seed } = sample;
12107
12114
  const size = rows ? `${rows} ROWS` : `${perc} PERCENT`;
12108
12115
  const how = method ? ` (${method}${seed != null ? `, ${seed}` : ""})` : "";
12109
- sql.push(`USING SAMPLE ${size}${how}`);
12116
+ sql2.push(`USING SAMPLE ${size}${how}`);
12110
12117
  }
12111
12118
  if (where.length) {
12112
12119
  const clauses = where.map(String).filter((x2) => x2).join(" AND ");
12113
12120
  if (clauses)
12114
- sql.push(`WHERE ${clauses}`);
12121
+ sql2.push(`WHERE ${clauses}`);
12115
12122
  }
12116
12123
  if (groupby.length) {
12117
- sql.push(`GROUP BY ${groupby.join(", ")}`);
12124
+ sql2.push(`GROUP BY ${groupby.join(", ")}`);
12118
12125
  }
12119
12126
  if (having.length) {
12120
12127
  const clauses = having.map(String).filter((x2) => x2).join(" AND ");
12121
12128
  if (clauses)
12122
- sql.push(`HAVING ${clauses}`);
12129
+ sql2.push(`HAVING ${clauses}`);
12123
12130
  }
12124
12131
  if (window.length) {
12125
12132
  const windows = window.map(({ as, expr: expr2 }) => `"${as}" AS (${expr2})`);
12126
- sql.push(`WINDOW ${windows.join(", ")}`);
12133
+ sql2.push(`WINDOW ${windows.join(", ")}`);
12127
12134
  }
12128
12135
  if (qualify.length) {
12129
12136
  const clauses = qualify.map(String).filter((x2) => x2).join(" AND ");
12130
12137
  if (clauses)
12131
- sql.push(`QUALIFY ${clauses}`);
12138
+ sql2.push(`QUALIFY ${clauses}`);
12132
12139
  }
12133
12140
  if (orderby.length) {
12134
- sql.push(`ORDER BY ${orderby.join(", ")}`);
12141
+ sql2.push(`ORDER BY ${orderby.join(", ")}`);
12135
12142
  }
12136
12143
  if (Number.isFinite(limit)) {
12137
- sql.push(`LIMIT ${limit}`);
12144
+ sql2.push(`LIMIT ${limit}`);
12138
12145
  }
12139
12146
  if (Number.isFinite(offset)) {
12140
- sql.push(`OFFSET ${offset}`);
12147
+ sql2.push(`OFFSET ${offset}`);
12141
12148
  }
12142
- return sql.join(" ");
12149
+ return sql2.join(" ");
12143
12150
  }
12144
12151
  };
12145
12152
  var SetOperation = class {
@@ -12190,17 +12197,17 @@ var SetOperation = class {
12190
12197
  }
12191
12198
  toString() {
12192
12199
  const { op, queries, query: { orderby, limit, offset } } = this;
12193
- const sql = [queries.join(` ${op} `)];
12200
+ const sql2 = [queries.join(` ${op} `)];
12194
12201
  if (orderby.length) {
12195
- sql.push(`ORDER BY ${orderby.join(", ")}`);
12202
+ sql2.push(`ORDER BY ${orderby.join(", ")}`);
12196
12203
  }
12197
12204
  if (Number.isFinite(limit)) {
12198
- sql.push(`LIMIT ${limit}`);
12205
+ sql2.push(`LIMIT ${limit}`);
12199
12206
  }
12200
12207
  if (Number.isFinite(offset)) {
12201
- sql.push(`OFFSET ${offset}`);
12208
+ sql2.push(`OFFSET ${offset}`);
12202
12209
  }
12203
- return sql.join(" ");
12210
+ return sql2.join(" ");
12204
12211
  }
12205
12212
  };
12206
12213
  function isQuery(value) {
@@ -12213,54 +12220,32 @@ function isDoubleQuoted(s) {
12213
12220
  return s[0] === '"' && s[s.length - 1] === '"';
12214
12221
  }
12215
12222
 
12216
- // src/util/js-type.js
12217
- function jsType(type) {
12218
- switch (type) {
12219
- case "BIGINT":
12220
- case "HUGEINT":
12221
- case "INTEGER":
12222
- case "SMALLINT":
12223
- case "TINYINT":
12224
- case "UBIGINT":
12225
- case "UINTEGER":
12226
- case "USMALLINT":
12227
- case "UTINYINT":
12228
- case "DOUBLE":
12229
- case "FLOAT":
12230
- case "REAL":
12231
- case "DECIMAL":
12232
- return "number";
12233
- case "DATE":
12234
- case "TIMESTAMP":
12235
- case "TIMESTAMPTZ":
12236
- case "TIME":
12237
- return "date";
12238
- case "BOOLEAN":
12239
- return "boolean";
12240
- case "VARCHAR":
12241
- case "UUID":
12242
- return "string";
12243
- case "LIST":
12244
- return "array";
12245
- case "BLOB":
12246
- case "STRUCT":
12247
- case "MAP":
12248
- return "object";
12249
- default:
12250
- throw new Error(`Unsupported type: ${type}`);
12251
- }
12223
+ // src/util/summarize.js
12224
+ var Count = "count";
12225
+ var Nulls = "nulls";
12226
+ var Max = "max";
12227
+ var Min = "min";
12228
+ var Distinct = "distinct";
12229
+ var statMap = {
12230
+ [Count]: count,
12231
+ [Distinct]: (column2) => count(column2).distinct(),
12232
+ [Max]: max,
12233
+ [Min]: min,
12234
+ [Nulls]: (column2) => count().where(isNull(column2))
12235
+ };
12236
+ function summarize({ table, column: column2 }, stats) {
12237
+ return Query.from(table).select(stats.map((s) => [s, statMap[s](column2)]));
12252
12238
  }
12253
12239
 
12254
12240
  // src/Catalog.js
12255
12241
  var object = () => /* @__PURE__ */ Object.create(null);
12256
12242
  var Catalog = class {
12257
- constructor(mc) {
12258
- this.mc = mc;
12243
+ constructor(coordinator2) {
12244
+ this.mc = coordinator2;
12259
12245
  this.clear();
12260
12246
  }
12261
12247
  clear() {
12262
12248
  this.tables = object();
12263
- this.fields = object();
12264
12249
  }
12265
12250
  async tableInfo(table) {
12266
12251
  const cache = this.tables;
@@ -12268,56 +12253,42 @@ var Catalog = class {
12268
12253
  return cache[table];
12269
12254
  }
12270
12255
  const q2 = this.mc.query(
12271
- `PRAGMA table_info('${table}')`,
12272
- { type: "json" }
12256
+ `DESCRIBE "${table}"`,
12257
+ { type: "json", cache: false }
12273
12258
  );
12274
12259
  return cache[table] = q2.then((result) => {
12275
12260
  const columns = object();
12276
12261
  for (const entry of result) {
12277
- columns[entry.name] = { ...entry, jstype: jsType(entry.type) };
12262
+ columns[entry.column_name] = {
12263
+ table,
12264
+ column: entry.column_name,
12265
+ sqlType: entry.column_type,
12266
+ type: jsType(entry.column_type),
12267
+ nullable: entry.null === "YES"
12268
+ };
12278
12269
  }
12279
12270
  return columns;
12280
12271
  });
12281
12272
  }
12282
- async fieldInfo(table, column2) {
12283
- const info = await this.tableInfo(table);
12284
- const colInfo = info[column2];
12273
+ async fieldInfo({ table, column: column2, stats }) {
12274
+ const tableInfo = await this.tableInfo(table);
12275
+ const colInfo = tableInfo[column2];
12285
12276
  if (colInfo == null)
12286
12277
  return;
12287
- const cache = this.fields;
12288
- const key = `${table}.${column2}`;
12289
- if (cache[key]) {
12290
- return cache[key];
12291
- }
12292
- const promise = this.mc.query(
12293
- Query.from(table).select({
12294
- rows: count(),
12295
- nulls: count().where(isNull(column2)),
12296
- min: min(column2),
12297
- max: max(column2)
12298
- }, { cache: false })
12299
- ).then((result) => {
12300
- const [stats] = Array.from(result);
12301
- return { table, column: column2, type: colInfo.jstype, ...stats };
12302
- });
12303
- return cache[key] = promise;
12278
+ if (!stats?.length)
12279
+ return colInfo;
12280
+ const result = await this.mc.query(summarize(colInfo, stats));
12281
+ const info = { ...colInfo, ...Array.from(result)[0] };
12282
+ return info;
12304
12283
  }
12305
12284
  async queryFields(fields) {
12306
12285
  const list = await resolveFields(this, fields);
12307
- const data = await Promise.all(
12308
- list.map((f2) => this.fieldInfo(f2.table, f2.column))
12309
- );
12286
+ const data = await Promise.all(list.map((f2) => this.fieldInfo(f2)));
12310
12287
  return data.filter((x2) => x2);
12311
12288
  }
12312
12289
  };
12313
12290
  async function resolveFields(catalog, list) {
12314
- if (list.length === 1 && list[0].column === "*") {
12315
- const table = list[0].table;
12316
- const info = await catalog.tableInfo(table);
12317
- return Object.keys(info).map((column2) => ({ table, column: column2 }));
12318
- } else {
12319
- return list;
12320
- }
12291
+ return list.length === 1 && list[0].column === "*" ? Object.values(await catalog.tableInfo(list[0].table)) : list;
12321
12292
  }
12322
12293
 
12323
12294
  // src/util/hash.js
@@ -12383,9 +12354,10 @@ var DataTileIndexer = class {
12383
12354
  const activeView = this.activeView = getActiveView(active);
12384
12355
  if (!activeView)
12385
12356
  return false;
12386
- console.warn("DATA TILE INDEX CONSTRUCTION");
12357
+ this.mc.logger().warn("DATA TILE INDEX CONSTRUCTION");
12387
12358
  const sel = this.selection.clone().update({ source });
12388
12359
  const indices = this.indices = /* @__PURE__ */ new Map();
12360
+ const promises = [];
12389
12361
  for (const client of clients) {
12390
12362
  if (sel.cross && skipClient(client, active))
12391
12363
  continue;
@@ -12396,13 +12368,13 @@ var DataTileIndexer = class {
12396
12368
  const cols = Object.values(activeView.columns).map((c) => c.columns[0]);
12397
12369
  subqueryPushdown(subq, cols);
12398
12370
  }
12399
- const sql = query.toString();
12400
- const id = (fnv_hash(sql) >>> 0).toString(16);
12371
+ const sql2 = query.toString();
12372
+ const id = (fnv_hash(sql2) >>> 0).toString(16);
12401
12373
  const table = `tile_index_${id}`;
12402
12374
  indices.set(client, { table, ...index });
12403
- createIndex(this.mc, table, sql);
12375
+ promises.push(createIndex(this.mc, table, sql2));
12404
12376
  }
12405
- return true;
12377
+ return promises;
12406
12378
  }
12407
12379
  async update() {
12408
12380
  const { clients, selection, activeView } = this;
@@ -12455,38 +12427,38 @@ function getActiveView(clause) {
12455
12427
  }
12456
12428
  function binInterval(scale) {
12457
12429
  const { type, domain, range: range2 } = scale;
12458
- let lift, sql;
12430
+ let lift, sql2;
12459
12431
  switch (type) {
12460
12432
  case "linear":
12461
12433
  lift = identity;
12462
- sql = asColumn;
12434
+ sql2 = asColumn;
12463
12435
  break;
12464
12436
  case "log":
12465
12437
  lift = Math.log;
12466
- sql = (c) => `LN(${asColumn(c)})`;
12438
+ sql2 = (c) => `LN(${asColumn(c)})`;
12467
12439
  break;
12468
12440
  case "symlog":
12469
12441
  lift = (x2) => Math.sign(x2) * Math.log1p(Math.abs(x2));
12470
- sql = (c) => (c = asColumn(c), `SIGN(${c}) * LN(1 + ABS(${c}))`);
12442
+ sql2 = (c) => (c = asColumn(c), `SIGN(${c}) * LN(1 + ABS(${c}))`);
12471
12443
  break;
12472
12444
  case "sqrt":
12473
12445
  lift = Math.sqrt;
12474
- sql = (c) => `SQRT(${asColumn(c)})`;
12446
+ sql2 = (c) => `SQRT(${asColumn(c)})`;
12475
12447
  break;
12476
12448
  case "utc":
12477
12449
  case "time":
12478
12450
  lift = (x2) => +x2;
12479
- sql = (c) => c instanceof Date ? +c : epoch_ms(asColumn(c));
12451
+ sql2 = (c) => c instanceof Date ? +c : epoch_ms(asColumn(c));
12480
12452
  break;
12481
12453
  }
12482
- return lift ? binFunction(domain, range2, lift, sql) : null;
12454
+ return lift ? binFunction(domain, range2, lift, sql2) : null;
12483
12455
  }
12484
- function binFunction(domain, range2, lift, sql) {
12456
+ function binFunction(domain, range2, lift, sql2) {
12485
12457
  const lo = lift(Math.min(domain[0], domain[1]));
12486
12458
  const hi = lift(Math.max(domain[0], domain[1]));
12487
12459
  const a = Math.abs(lift(range2[1]) - lift(range2[0])) / (hi - lo);
12488
12460
  return (value) => expr(
12489
- `FLOOR(${a}::DOUBLE * (${sql(value)} - ${lo}::DOUBLE))`,
12461
+ `FLOOR(${a}::DOUBLE * (${sql2(value)} - ${lo}::DOUBLE))`,
12490
12462
  asColumn(value).columns
12491
12463
  );
12492
12464
  }
@@ -12494,15 +12466,17 @@ async function createIndex(mc, table, query) {
12494
12466
  try {
12495
12467
  await mc.exec(`CREATE TEMP TABLE IF NOT EXISTS ${table} AS ${query}`);
12496
12468
  } catch (err) {
12497
- console.error(err);
12469
+ mc.logger().error(err);
12498
12470
  }
12499
12471
  }
12472
+ var NO_INDEX = { from: NaN };
12500
12473
  function getIndexColumns(client) {
12474
+ if (!client.filterIndexable)
12475
+ return NO_INDEX;
12501
12476
  const q2 = client.query();
12502
12477
  const from = getBaseTable(q2);
12503
- if (!from || !q2.groupby || !client.filterIndexable) {
12504
- return { from: NaN };
12505
- }
12478
+ if (!from || !q2.groupby)
12479
+ return NO_INDEX;
12506
12480
  const g2 = new Set(q2.groupby().map((c) => c.column));
12507
12481
  let aggr = [];
12508
12482
  let dims = [];
@@ -12571,13 +12545,15 @@ function subqueryPushdown(query, cols) {
12571
12545
  }
12572
12546
 
12573
12547
  // src/util/throttle.js
12574
- function throttle(callback) {
12548
+ var NIL = {};
12549
+ function throttle(callback, debounce = false) {
12575
12550
  let curr;
12576
12551
  let next;
12552
+ let pending = NIL;
12577
12553
  function invoke(event) {
12578
12554
  curr = callback(event).then(() => {
12579
12555
  if (next) {
12580
- const value = next;
12556
+ const { value } = next;
12581
12557
  next = null;
12582
12558
  invoke(value);
12583
12559
  } else {
@@ -12586,18 +12562,31 @@ function throttle(callback) {
12586
12562
  });
12587
12563
  }
12588
12564
  function enqueue(event) {
12589
- next = event;
12565
+ next = { event };
12566
+ }
12567
+ function process(event) {
12568
+ curr ? enqueue(event) : invoke(event);
12569
+ }
12570
+ function delay(event) {
12571
+ if (pending !== event) {
12572
+ requestAnimationFrame(() => {
12573
+ const e = pending;
12574
+ pending = NIL;
12575
+ process(e);
12576
+ });
12577
+ }
12578
+ pending = event;
12590
12579
  }
12591
- return (event) => curr ? enqueue(event) : invoke(event);
12580
+ return debounce ? delay : process;
12592
12581
  }
12593
12582
 
12594
12583
  // src/FilterGroup.js
12595
12584
  var FilterGroup = class {
12596
- constructor(mc, selection, index = true) {
12597
- this.mc = mc;
12585
+ constructor(coordinator2, selection, index = true) {
12586
+ this.mc = coordinator2;
12598
12587
  this.selection = selection;
12599
12588
  this.clients = /* @__PURE__ */ new Set();
12600
- this.indexer = index ? new DataTileIndexer(mc, selection) : null;
12589
+ this.indexer = index ? new DataTileIndexer(this.mc, selection) : null;
12601
12590
  const { value, activate } = this.handlers = {
12602
12591
  value: throttle(() => this.update()),
12603
12592
  activate: (clause) => {
@@ -12640,6 +12629,13 @@ function defaultUpdate(mc, clients, selection) {
12640
12629
  }
12641
12630
 
12642
12631
  // src/QueryCache.js
12632
+ var requestIdle = typeof requestIdleCallback !== "undefined" ? requestIdleCallback : setTimeout;
12633
+ var voidCache = () => ({
12634
+ get: () => void 0,
12635
+ set: (key, result) => result,
12636
+ clear: () => {
12637
+ }
12638
+ });
12643
12639
  var QueryCache = class {
12644
12640
  constructor({
12645
12641
  max: max2 = 1e3,
@@ -12663,16 +12659,10 @@ var QueryCache = class {
12663
12659
  }
12664
12660
  set(key, promise) {
12665
12661
  const { cache, max: max2 } = this;
12666
- const now = performance.now();
12667
- const receive = promise.then((result) => {
12668
- console.log(`Query: ${Math.round(performance.now() - now)}`);
12669
- return result;
12670
- });
12671
- cache.set(key, { last: now, promise });
12672
- if (cache.size > max2) {
12673
- setTimeout(() => this.evict());
12674
- }
12675
- return receive;
12662
+ cache.set(key, { last: performance.now(), promise });
12663
+ if (cache.size > max2)
12664
+ requestIdle(() => this.evict());
12665
+ return promise;
12676
12666
  }
12677
12667
  evict() {
12678
12668
  const expire = performance.now() - this.ttl;
@@ -12694,6 +12684,22 @@ var QueryCache = class {
12694
12684
  }
12695
12685
  };
12696
12686
 
12687
+ // src/util/void-logger.js
12688
+ function voidLogger() {
12689
+ return {
12690
+ debug() {
12691
+ },
12692
+ info() {
12693
+ },
12694
+ log() {
12695
+ },
12696
+ warn() {
12697
+ },
12698
+ error() {
12699
+ }
12700
+ };
12701
+ }
12702
+
12697
12703
  // src/Coordinator.js
12698
12704
  var _instance;
12699
12705
  function coordinator(instance17) {
@@ -12705,27 +12711,25 @@ function coordinator(instance17) {
12705
12711
  return _instance;
12706
12712
  }
12707
12713
  var Coordinator = class {
12708
- constructor(db = socketClient()) {
12709
- this.cache = new QueryCache();
12714
+ constructor(db = socketClient(), options = {}) {
12710
12715
  this.catalog = new Catalog(this);
12711
- this.indexes = true;
12716
+ this.logger(options.logger || console);
12717
+ this.configure(options);
12712
12718
  this.databaseClient(db);
12713
12719
  this.clear();
12714
12720
  }
12721
+ logger(logger) {
12722
+ return arguments.length ? this._logger = logger || voidLogger() : this._logger;
12723
+ }
12715
12724
  configure({ cache = true, indexes = true }) {
12716
- this.cache = cache ? new QueryCache() : {
12717
- get: () => void 0,
12718
- set: (key, result) => result,
12719
- clear: () => {
12720
- }
12721
- };
12725
+ this.cache = cache ? new QueryCache() : voidCache();
12722
12726
  this.indexes = indexes;
12723
12727
  }
12724
12728
  clear({ clients = true, cache = true, catalog = false } = {}) {
12725
12729
  if (clients) {
12726
- this.clients?.forEach((_2, client) => this.disconnect(client));
12730
+ this.clients?.forEach((client) => this.disconnect(client));
12727
12731
  this.filterGroups?.forEach((group) => group.finalize());
12728
- this.clients = /* @__PURE__ */ new Map();
12732
+ this.clients = /* @__PURE__ */ new Set();
12729
12733
  this.filterGroups = /* @__PURE__ */ new Map();
12730
12734
  }
12731
12735
  if (cache)
@@ -12739,21 +12743,25 @@ var Coordinator = class {
12739
12743
  }
12740
12744
  return this.db;
12741
12745
  }
12742
- async exec(sql) {
12746
+ async exec(sql2) {
12743
12747
  try {
12744
- await this.db.query({ type: "exec", sql });
12748
+ await this.db.query({ type: "exec", sql: sql2 });
12745
12749
  } catch (err) {
12746
- console.error(err);
12750
+ this._logger.error(err);
12747
12751
  }
12748
12752
  }
12749
- async query(query, { type = "arrow", cache = true } = {}) {
12750
- const sql = String(query);
12751
- const cached = this.cache.get(sql);
12753
+ query(query, { type = "arrow", cache = true } = {}) {
12754
+ const sql2 = String(query);
12755
+ const t0 = performance.now();
12756
+ const cached = this.cache.get(sql2);
12752
12757
  if (cached) {
12758
+ this._logger.debug("Cache");
12753
12759
  return cached;
12754
12760
  } else {
12755
- const request = this.db.query({ type, sql });
12756
- return cache ? this.cache.set(sql, request) : request;
12761
+ const request = this.db.query({ type, sql: sql2 });
12762
+ const result = cache ? this.cache.set(sql2, request) : request;
12763
+ result.then(() => this._logger.debug(`Query: ${performance.now() - t0}`));
12764
+ return result;
12757
12765
  }
12758
12766
  }
12759
12767
  async updateClient(client, query) {
@@ -12762,22 +12770,26 @@ var Coordinator = class {
12762
12770
  client.queryPending();
12763
12771
  result = await this.query(query);
12764
12772
  } catch (err) {
12765
- console.error(err);
12773
+ this._logger.error(err);
12766
12774
  client.queryError(err);
12767
12775
  return;
12768
12776
  }
12769
12777
  try {
12770
12778
  client.queryResult(result).update();
12771
12779
  } catch (err) {
12772
- console.error(err);
12780
+ this._logger.error(err);
12773
12781
  }
12774
12782
  }
12783
+ async requestQuery(client, query) {
12784
+ this.filterGroups.get(client.filterBy)?.reset();
12785
+ return query ? this.updateClient(client, query) : client.update();
12786
+ }
12775
12787
  async connect(client) {
12776
12788
  const { catalog, clients, filterGroups, indexes } = this;
12777
12789
  if (clients.has(client)) {
12778
12790
  throw new Error("Client already connected.");
12779
12791
  }
12780
- clients.set(client, null);
12792
+ clients.add(client);
12781
12793
  const fields = client.fields();
12782
12794
  if (fields?.length) {
12783
12795
  client.fieldStats(await catalog.queryFields(fields));
@@ -12791,41 +12803,139 @@ var Coordinator = class {
12791
12803
  filterGroups.set(filter, group.add(client));
12792
12804
  }
12793
12805
  }
12794
- const handler = async (query) => {
12795
- const q2 = query || client.query(filter?.predicate(client));
12796
- filterGroups.get(filter)?.reset();
12797
- if (q2)
12798
- this.updateClient(client, q2);
12799
- };
12800
- clients.set(client, handler);
12801
- client.request?.addEventListener("value", handler);
12802
- handler();
12806
+ client.requestQuery();
12803
12807
  }
12804
12808
  disconnect(client) {
12805
12809
  const { clients, filterGroups } = this;
12806
12810
  if (!clients.has(client))
12807
12811
  return;
12808
- const handler = clients.get(client);
12809
12812
  clients.delete(client);
12810
12813
  filterGroups.get(client.filterBy)?.remove(client);
12811
- client.request?.removeEventListener(handler);
12812
12814
  }
12813
12815
  };
12814
12816
 
12815
- // src/Signal.js
12816
- function isSignal(x2) {
12817
- return x2 instanceof Signal;
12817
+ // src/MosaicClient.js
12818
+ var MosaicClient = class {
12819
+ /**
12820
+ * Constructor.
12821
+ * @param {*} filterSelection An optional selection to interactively filter
12822
+ * this client's data. If provided, a coordinator will re-query and update
12823
+ * the client when the selection updates.
12824
+ */
12825
+ constructor(filterSelection) {
12826
+ this._filterBy = filterSelection;
12827
+ this._requestUpdate = throttle(() => this.requestQuery(), true);
12828
+ }
12829
+ /**
12830
+ * Return this client's filter selection.
12831
+ */
12832
+ get filterBy() {
12833
+ return this._filterBy;
12834
+ }
12835
+ /**
12836
+ * Return a boolean indicating if the client query can be indexed. Should
12837
+ * return true if changes to the filterBy selection does not change the
12838
+ * groupby domain of the client query.
12839
+ */
12840
+ get filterIndexable() {
12841
+ return true;
12842
+ }
12843
+ /**
12844
+ * Return an array of fields queried by this client.
12845
+ */
12846
+ fields() {
12847
+ return null;
12848
+ }
12849
+ /**
12850
+ * Called by the coordinator to set the field statistics for this client.
12851
+ * @returns {this}
12852
+ */
12853
+ fieldStats() {
12854
+ return this;
12855
+ }
12856
+ /**
12857
+ * Return a query specifying the data needed by this client.
12858
+ */
12859
+ query() {
12860
+ return null;
12861
+ }
12862
+ /**
12863
+ * Called by the coordinator to inform the client that a query is pending.
12864
+ */
12865
+ queryPending() {
12866
+ return this;
12867
+ }
12868
+ /**
12869
+ * Called by the coordinator to return a query result.
12870
+ */
12871
+ queryResult() {
12872
+ return this;
12873
+ }
12874
+ /**
12875
+ * Called by the coordinator to report a query execution error.
12876
+ */
12877
+ queryError(error) {
12878
+ console.error(error);
12879
+ return this;
12880
+ }
12881
+ /**
12882
+ * Request the coordinator to execute a query for this client.
12883
+ * If an explicit query is not provided, the client query method will
12884
+ * be called, filtered by the current filterBy selection.
12885
+ */
12886
+ requestQuery(query) {
12887
+ const q2 = query || this.query(this.filterBy?.predicate(this));
12888
+ return coordinator().requestQuery(this, q2);
12889
+ }
12890
+ /**
12891
+ * Request that the coordinator perform a throttled update of this client
12892
+ * using the default query. Unlike requestQuery, for which every call will
12893
+ * result in an executed query, multiple calls to requestUpdate may be
12894
+ * consolidated into a single update.
12895
+ */
12896
+ requestUpdate() {
12897
+ this._requestUpdate();
12898
+ }
12899
+ /**
12900
+ * Requests a client update.
12901
+ * For example to (re-)render an interface component.
12902
+ */
12903
+ update() {
12904
+ return this;
12905
+ }
12906
+ };
12907
+
12908
+ // src/util/distinct.js
12909
+ function distinct(a, b2) {
12910
+ return a === b2 ? false : a instanceof Date && b2 instanceof Date ? +a !== +b2 : Array.isArray(a) && Array.isArray(b2) ? distinctArray(a, b2) : true;
12911
+ }
12912
+ function distinctArray(a, b2) {
12913
+ if (a.length !== b2.length)
12914
+ return true;
12915
+ for (let i2 = 0; i2 < a.length; ++i2) {
12916
+ if (a[i2] !== b2[i2])
12917
+ return true;
12918
+ }
12919
+ return false;
12920
+ }
12921
+
12922
+ // src/Param.js
12923
+ function isParam(x2) {
12924
+ return x2 instanceof Param;
12818
12925
  }
12819
- var Signal = class {
12926
+ var Param = class {
12820
12927
  constructor(value) {
12821
12928
  this._value = value;
12822
12929
  this._listeners = /* @__PURE__ */ new Map();
12823
12930
  }
12931
+ static value(value) {
12932
+ return new Param(value);
12933
+ }
12824
12934
  get value() {
12825
12935
  return this._value;
12826
12936
  }
12827
12937
  update(value, { force } = {}) {
12828
- const changed = this._value !== value;
12938
+ const changed = distinct(this._value, value);
12829
12939
  if (changed)
12830
12940
  this._value = value;
12831
12941
  if (changed || force)
@@ -12834,7 +12944,7 @@ var Signal = class {
12834
12944
  }
12835
12945
  addEventListener(type, callback) {
12836
12946
  let list = this._listeners.get(type) || [];
12837
- if (list.indexOf(callback) < 0) {
12947
+ if (!list.includes(callback)) {
12838
12948
  list = list.concat(callback);
12839
12949
  }
12840
12950
  this._listeners.set(type, list);
@@ -12854,7 +12964,7 @@ var Signal = class {
12854
12964
  function isSelection(x2) {
12855
12965
  return x2 instanceof Selection;
12856
12966
  }
12857
- var Selection = class extends Signal {
12967
+ var Selection = class extends Param {
12858
12968
  static intersect() {
12859
12969
  return new Selection();
12860
12970
  }
@@ -24029,8 +24139,8 @@ async function wasmClient(options) {
24029
24139
  db,
24030
24140
  con,
24031
24141
  query: async (query) => {
24032
- const { type, sql } = query;
24033
- const result = await con.query(sql);
24142
+ const { type, sql: sql2 } = query;
24143
+ const result = await con.query(sql2);
24034
24144
  return type === "exec" ? void 0 : type === "arrow" ? result : Array.from(result);
24035
24145
  }
24036
24146
  };
@@ -24053,11 +24163,12 @@ async function initDatabase({
24053
24163
  export {
24054
24164
  Coordinator,
24055
24165
  MosaicClient,
24166
+ Param,
24056
24167
  Selection,
24057
- Signal,
24058
24168
  coordinator,
24169
+ distinct,
24170
+ isParam,
24059
24171
  isSelection,
24060
- isSignal,
24061
24172
  restClient,
24062
24173
  socketClient,
24063
24174
  sqlFrom,