@signalk/tracks-plugin 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { c as createMatcher, n as SelfPositionUnavailableError, p as splitAtAntimeridian, s as createInBounds, x as thin } from "./utils-CGIGp4Xf.js";
1
+ import { S as thin, c as createMatcher, d as longitudeSpan, m as splitAtAntimeridian, n as SelfPositionUnavailableError, s as createInBounds } from "./utils-C0VkiidM.js";
2
2
  import { parentPort, workerData } from "node:worker_threads";
3
3
  import { DatabaseSync } from "node:sqlite";
4
4
  //#region \0rolldown/runtime.js
@@ -11305,6 +11305,11 @@ var coveringFor = (bounds, maxCells) => {
11305
11305
  });
11306
11306
  });
11307
11307
  };
11308
+ /** Longitude intervals as `[west, east]`, either of which may cross the antimeridian. */
11309
+ var longitudesOverlap = (a, b) => {
11310
+ const pieces = ([west, east]) => west <= east ? [[west, east]] : [[west, 180], [-180, east]];
11311
+ return pieces(a).some(([aWest, aEast]) => pieces(b).some(([bWest, bEast]) => aWest <= bEast && bWest <= aEast));
11312
+ };
11308
11313
  var DEFAULT_MAX_CELLS = 8;
11309
11314
  var DEFAULT_SEGMENT_GAP = 3e5;
11310
11315
  /**
@@ -11330,9 +11335,11 @@ var SqliteTrackStore = class {
11330
11335
  insert;
11331
11336
  insertName;
11332
11337
  selectName;
11338
+ selectImportedPoints;
11333
11339
  maxCells;
11334
11340
  segmentGap;
11335
11341
  retention;
11342
+ binRetention;
11336
11343
  resolution;
11337
11344
  /** See CLIP_STRETCH_GAP; widened so a coarse write resolution does not split every fix. */
11338
11345
  stretchGap;
@@ -11349,6 +11356,7 @@ var SqliteTrackStore = class {
11349
11356
  this.maxCells = config.maxCells ?? DEFAULT_MAX_CELLS;
11350
11357
  this.segmentGap = config.segmentGap ?? DEFAULT_SEGMENT_GAP;
11351
11358
  this.retention = config.retention ?? 0;
11359
+ this.binRetention = config.binRetention ?? 0;
11352
11360
  this.resolution = config.resolution ?? 0;
11353
11361
  this.stretchGap = Math.max(CLIP_STRETCH_GAP, 2 * this.resolution);
11354
11362
  this.debug = debug;
@@ -11374,12 +11382,85 @@ var SqliteTrackStore = class {
11374
11382
  name TEXT NOT NULL,
11375
11383
  timestamp INTEGER NOT NULL
11376
11384
  );
11385
+ -- Imported tracks, apart from positions: they are never merged into a
11386
+ -- recording or pruned with one. The extent is kept per track so a query
11387
+ -- can skip an import without reading its points.
11388
+ CREATE TABLE IF NOT EXISTS imported_tracks (
11389
+ id TEXT PRIMARY KEY,
11390
+ context TEXT,
11391
+ name TEXT,
11392
+ metadata TEXT NOT NULL,
11393
+ first_time INTEGER,
11394
+ last_time INTEGER,
11395
+ west REAL NOT NULL,
11396
+ south REAL NOT NULL,
11397
+ east REAL NOT NULL,
11398
+ north REAL NOT NULL
11399
+ );
11400
+ CREATE INDEX IF NOT EXISTS idx_imported_context ON imported_tracks(context);
11401
+ CREATE TABLE IF NOT EXISTS imported_points (
11402
+ track_id TEXT NOT NULL,
11403
+ seq INTEGER NOT NULL,
11404
+ segment INTEGER NOT NULL,
11405
+ timestamp INTEGER,
11406
+ lat REAL NOT NULL,
11407
+ lon REAL NOT NULL,
11408
+ PRIMARY KEY (track_id, seq)
11409
+ ) WITHOUT ROWID;
11410
+ -- The recycle bin. A delete moves rows here rather than removing them,
11411
+ -- so the tables every read uses stay as they are and a restore moves
11412
+ -- them back. A purged recorded span keeps its row: a history provider
11413
+ -- would otherwise fill the deleted window back in.
11414
+ CREATE TABLE IF NOT EXISTS recycle_bin (
11415
+ id INTEGER PRIMARY KEY,
11416
+ context TEXT,
11417
+ import_id TEXT,
11418
+ name TEXT,
11419
+ whole INTEGER NOT NULL,
11420
+ span_from INTEGER,
11421
+ span_to INTEGER,
11422
+ deleted_at INTEGER NOT NULL,
11423
+ purged INTEGER NOT NULL DEFAULT 0
11424
+ );
11425
+ CREATE TABLE IF NOT EXISTS binned_positions (
11426
+ bin_id INTEGER NOT NULL,
11427
+ context TEXT NOT NULL,
11428
+ timestamp INTEGER NOT NULL,
11429
+ lat REAL NOT NULL,
11430
+ lon REAL NOT NULL,
11431
+ s2cell INTEGER NOT NULL
11432
+ );
11433
+ CREATE INDEX IF NOT EXISTS idx_binned_positions ON binned_positions(bin_id);
11434
+ CREATE TABLE IF NOT EXISTS binned_imported_tracks (
11435
+ bin_id INTEGER PRIMARY KEY,
11436
+ id TEXT NOT NULL,
11437
+ context TEXT,
11438
+ name TEXT,
11439
+ metadata TEXT NOT NULL,
11440
+ first_time INTEGER,
11441
+ last_time INTEGER,
11442
+ west REAL NOT NULL,
11443
+ south REAL NOT NULL,
11444
+ east REAL NOT NULL,
11445
+ north REAL NOT NULL
11446
+ );
11447
+ CREATE TABLE IF NOT EXISTS binned_imported_points (
11448
+ bin_id INTEGER NOT NULL,
11449
+ track_id TEXT NOT NULL,
11450
+ seq INTEGER NOT NULL,
11451
+ segment INTEGER NOT NULL,
11452
+ timestamp INTEGER,
11453
+ lat REAL NOT NULL,
11454
+ lon REAL NOT NULL
11455
+ );
11456
+ CREATE INDEX IF NOT EXISTS idx_binned_imported_points ON binned_imported_points(bin_id);
11377
11457
  `);
11378
11458
  this.insert = this.db.prepare("INSERT INTO positions (context, timestamp, lat, lon, s2cell) VALUES (?, ?, ?, ?, ?)");
11379
11459
  this.insertName = this.db.prepare(`INSERT INTO names (context, name, timestamp) VALUES (?, ?, ?)
11380
11460
  ON CONFLICT(context) DO UPDATE SET name = excluded.name, timestamp = excluded.timestamp
11381
11461
  WHERE excluded.timestamp >= names.timestamp`);
11382
11462
  this.selectName = this.db.prepare("SELECT name FROM names WHERE context = ?");
11463
+ this.selectImportedPoints = this.db.prepare("SELECT segment, timestamp, lat, lon FROM imported_points WHERE track_id = ? ORDER BY seq");
11383
11464
  }
11384
11465
  newPosition(context, position, timestamp = Date.now()) {
11385
11466
  if (this.resolution > 0) {
@@ -11594,12 +11675,244 @@ var SqliteTrackStore = class {
11594
11675
  this.lastName.delete(context);
11595
11676
  }
11596
11677
  for (const { context } of dropped) this.lastStored.delete(context);
11678
+ if (this.binRetention > 0) {
11679
+ const expired = this.db.prepare("SELECT * FROM recycle_bin WHERE purged = 0 AND deleted_at < ? ORDER BY whole DESC").all(now - this.binRetention);
11680
+ for (const entry of expired) if (this.liveEntry(entry.id)) this.purge(entry);
11681
+ }
11597
11682
  if (this.retention > 0) {
11598
11683
  const oldest = now - this.retention;
11599
11684
  if (keep === void 0) this.db.prepare("DELETE FROM positions WHERE timestamp < ?").run(oldest);
11600
11685
  else this.db.prepare("DELETE FROM positions WHERE timestamp < ? AND context IS ?").run(oldest, keep);
11601
11686
  }
11602
11687
  }
11688
+ /**
11689
+ * Keep an imported track. The caller mints the id; a second track under the
11690
+ * same id fails rather than replacing the first.
11691
+ */
11692
+ storeImport(track) {
11693
+ const points = track.segments.flat();
11694
+ if (points.length === 0) throw new Error("An imported track needs at least one point");
11695
+ let south = points[0].position[0];
11696
+ let north = south;
11697
+ let firstTime;
11698
+ let lastTime;
11699
+ for (const { position, timestamp } of points) {
11700
+ south = Math.min(south, position[0]);
11701
+ north = Math.max(north, position[0]);
11702
+ if (timestamp !== void 0) {
11703
+ firstTime = firstTime === void 0 ? timestamp : Math.min(firstTime, timestamp);
11704
+ lastTime = lastTime === void 0 ? timestamp : Math.max(lastTime, timestamp);
11705
+ }
11706
+ }
11707
+ const [west, east] = longitudeSpan(points.map(({ position }) => position[1]));
11708
+ this.db.prepare(`INSERT INTO imported_tracks (id, context, name, metadata, first_time, last_time, west, south, east, north)
11709
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(track.id, track.context ?? null, track.name ?? null, JSON.stringify(track.metadata), firstTime ?? null, lastTime ?? null, west, south, east, north);
11710
+ const insert = this.db.prepare("INSERT INTO imported_points (track_id, seq, segment, timestamp, lat, lon) VALUES (?, ?, ?, ?, ?, ?)");
11711
+ let seq = 0;
11712
+ for (const [segment, segmentPoints] of track.segments.entries()) for (const { position, timestamp } of segmentPoints) insert.run(track.id, seq++, segment, timestamp ?? null, position[0], position[1]);
11713
+ }
11714
+ /**
11715
+ * Move a vessel's positions recorded within `[from, to]` into the recycle
11716
+ * bin, resolving to the entry's id and whether the store held the vessel at
11717
+ * all.
11718
+ *
11719
+ * An entry is made even when the store holds nothing there if `keepEmpty`
11720
+ * says another source does: the span still has to hide what a history
11721
+ * provider holds. Without one, an empty delete leaves no entry behind.
11722
+ */
11723
+ binRecorded(context, from, to, deletedAt, keepEmpty) {
11724
+ const known = this.knows(context);
11725
+ const lower = from ?? Number.MIN_SAFE_INTEGER;
11726
+ const id = this.newBinEntry({
11727
+ context,
11728
+ ...from === void 0 ? {} : { from },
11729
+ to,
11730
+ deletedAt
11731
+ });
11732
+ if (Number(this.db.prepare(`INSERT INTO binned_positions (bin_id, context, timestamp, lat, lon, s2cell)
11733
+ SELECT ?, context, timestamp, lat, lon, s2cell FROM positions
11734
+ WHERE context = ? AND timestamp >= ? AND timestamp <= ?`).run(id, context, lower, to).changes) === 0 && !keepEmpty) {
11735
+ this.db.prepare("DELETE FROM recycle_bin WHERE id = ?").run(id);
11736
+ return { known };
11737
+ }
11738
+ this.db.prepare("DELETE FROM positions WHERE context = ? AND timestamp >= ? AND timestamp <= ?").run(context, lower, to);
11739
+ return {
11740
+ id,
11741
+ known
11742
+ };
11743
+ }
11744
+ /**
11745
+ * Move an imported track into the recycle bin, or with a span only its
11746
+ * points within it, resolving to the entry's id; undefined when there is no
11747
+ * such import, or the span holds none of its points.
11748
+ */
11749
+ binImport(importId, span, deletedAt) {
11750
+ const track = this.db.prepare("SELECT context, name FROM imported_tracks WHERE id = ?").get(importId);
11751
+ if (!track) return;
11752
+ const id = this.newBinEntry({
11753
+ ...track.context === null ? {} : { context: track.context },
11754
+ importId,
11755
+ ...track.name === null ? {} : { name: track.name },
11756
+ ...span ?? {},
11757
+ deletedAt
11758
+ });
11759
+ const where = span ? "track_id = ? AND timestamp >= ? AND timestamp <= ?" : "track_id = ?";
11760
+ const params = span ? [
11761
+ importId,
11762
+ span.from ?? Number.MIN_SAFE_INTEGER,
11763
+ span.to ?? Number.MAX_SAFE_INTEGER
11764
+ ] : [importId];
11765
+ const moved = Number(this.db.prepare(`INSERT INTO binned_imported_points (bin_id, track_id, seq, segment, timestamp, lat, lon)
11766
+ SELECT ?, track_id, seq, segment, timestamp, lat, lon FROM imported_points WHERE ${where}`).run(id, ...params).changes);
11767
+ if (span && moved === 0) {
11768
+ this.db.prepare("DELETE FROM recycle_bin WHERE id = ?").run(id);
11769
+ return;
11770
+ }
11771
+ this.db.prepare(`DELETE FROM imported_points WHERE ${where}`).run(...params);
11772
+ const emptied = span && !this.db.prepare("SELECT 1 AS found FROM imported_points WHERE track_id = ?").get(importId);
11773
+ if (emptied) this.db.prepare("UPDATE recycle_bin SET whole = 1, span_from = NULL, span_to = NULL WHERE id = ?").run(id);
11774
+ if (!span || emptied) {
11775
+ this.db.prepare(`INSERT INTO binned_imported_tracks
11776
+ SELECT ?, id, context, name, metadata, first_time, last_time, west, south, east, north
11777
+ FROM imported_tracks WHERE id = ?`).run(id, importId);
11778
+ this.db.prepare("DELETE FROM imported_tracks WHERE id = ?").run(importId);
11779
+ }
11780
+ return id;
11781
+ }
11782
+ newBinEntry(entry) {
11783
+ const whole = entry.importId !== void 0 && entry.from === void 0 && entry.to === void 0;
11784
+ return Number(this.db.prepare(`INSERT INTO recycle_bin (context, import_id, name, whole, span_from, span_to, deleted_at)
11785
+ VALUES (?, ?, ?, ?, ?, ?, ?)`).run(entry.context ?? null, entry.importId ?? null, entry.name ?? null, whole ? 1 : 0, entry.from ?? null, entry.to ?? null, entry.deletedAt).lastInsertRowid);
11786
+ }
11787
+ /**
11788
+ * Put a recycle bin entry's rows back where they were deleted from.
11789
+ *
11790
+ * A span of an import whose whole track has since gone to the bin as well
11791
+ * is a conflict: its points have no track to return to until that is
11792
+ * restored first.
11793
+ */
11794
+ restoreFromBin(id) {
11795
+ const entry = this.liveEntry(id);
11796
+ if (!entry) return "missing";
11797
+ if (entry.import_id === null) {
11798
+ this.db.prepare(`INSERT INTO positions (context, timestamp, lat, lon, s2cell)
11799
+ SELECT context, timestamp, lat, lon, s2cell FROM binned_positions WHERE bin_id = ?`).run(id);
11800
+ this.db.prepare("DELETE FROM binned_positions WHERE bin_id = ?").run(id);
11801
+ } else {
11802
+ if (entry.whole === 1) {
11803
+ this.db.prepare(`INSERT INTO imported_tracks (id, context, name, metadata, first_time, last_time, west, south, east, north)
11804
+ SELECT id, context, name, metadata, first_time, last_time, west, south, east, north
11805
+ FROM binned_imported_tracks WHERE bin_id = ?`).run(id);
11806
+ this.db.prepare("DELETE FROM binned_imported_tracks WHERE bin_id = ?").run(id);
11807
+ } else if (!this.db.prepare("SELECT 1 AS found FROM imported_tracks WHERE id = ?").get(entry.import_id)) return "conflict";
11808
+ this.db.prepare(`INSERT INTO imported_points (track_id, seq, segment, timestamp, lat, lon)
11809
+ SELECT track_id, seq, segment, timestamp, lat, lon FROM binned_imported_points WHERE bin_id = ?`).run(id);
11810
+ this.db.prepare("DELETE FROM binned_imported_points WHERE bin_id = ?").run(id);
11811
+ }
11812
+ this.db.prepare("DELETE FROM recycle_bin WHERE id = ?").run(id);
11813
+ return "restored";
11814
+ }
11815
+ /** Purge one recycle bin entry now, resolving whether it was in the bin. */
11816
+ purgeFromBin(id) {
11817
+ const entry = this.liveEntry(id);
11818
+ if (!entry) return false;
11819
+ this.purge(entry);
11820
+ return true;
11821
+ }
11822
+ /** Entries still in the recycle bin, newest delete first. */
11823
+ binEntries() {
11824
+ return this.db.prepare(`SELECT bin.*,
11825
+ (SELECT COUNT(*) FROM binned_positions WHERE bin_id = bin.id)
11826
+ + (SELECT COUNT(*) FROM binned_imported_points WHERE bin_id = bin.id) AS point_count
11827
+ FROM recycle_bin AS bin WHERE purged = 0 ORDER BY deleted_at DESC, id DESC`).all().map((row) => ({
11828
+ id: row.id,
11829
+ ...row.context === null ? {} : { context: row.context },
11830
+ ...row.import_id === null ? {} : { importId: row.import_id },
11831
+ ...row.name === null ? {} : { name: row.name },
11832
+ whole: row.whole === 1,
11833
+ ...row.span_from === null ? {} : { from: row.span_from },
11834
+ ...row.span_to === null ? {} : { to: row.span_to },
11835
+ deletedAt: row.deleted_at,
11836
+ pointCount: row.point_count
11837
+ }));
11838
+ }
11839
+ /** Every deleted span of a recorded track, purged or not: what history must not show. */
11840
+ deletedSpans() {
11841
+ return this.db.prepare("SELECT context, span_from, span_to FROM recycle_bin WHERE import_id IS NULL").all().map(({ context, span_from, span_to }) => ({
11842
+ context,
11843
+ ...span_from === null ? {} : { from: span_from },
11844
+ to: span_to
11845
+ }));
11846
+ }
11847
+ liveEntry(id) {
11848
+ return this.db.prepare("SELECT * FROM recycle_bin WHERE id = ? AND purged = 0").get(id);
11849
+ }
11850
+ /**
11851
+ * Remove an entry's rows for good. A recorded span stays as a tombstone, so
11852
+ * history cannot refill it; a whole import takes the spans deleted from it
11853
+ * earlier with it, since they have no track left to be restored into.
11854
+ */
11855
+ purge(entry) {
11856
+ const ids = entry.whole === 1 ? this.db.prepare("SELECT id FROM recycle_bin WHERE import_id = ? AND purged = 0").all(entry.import_id).map(({ id }) => id) : [entry.id];
11857
+ for (const id of ids) {
11858
+ this.db.prepare("DELETE FROM binned_positions WHERE bin_id = ?").run(id);
11859
+ this.db.prepare("DELETE FROM binned_imported_points WHERE bin_id = ?").run(id);
11860
+ this.db.prepare("DELETE FROM binned_imported_tracks WHERE bin_id = ?").run(id);
11861
+ if (entry.import_id === null) this.db.prepare("UPDATE recycle_bin SET purged = 1 WHERE id = ?").run(id);
11862
+ else this.db.prepare("DELETE FROM recycle_bin WHERE id = ?").run(id);
11863
+ }
11864
+ }
11865
+ getImport(id) {
11866
+ const row = this.db.prepare("SELECT * FROM imported_tracks WHERE id = ?").get(id);
11867
+ return row ? this.importFrom(row) : void 0;
11868
+ }
11869
+ /** Imported tracks the filter lets through, in the order they were stored. */
11870
+ findImports(filter = {}) {
11871
+ const clauses = [];
11872
+ const params = [];
11873
+ if (filter.contexts) {
11874
+ if (filter.contexts.length === 0) return [];
11875
+ clauses.push(`context IN (${filter.contexts.map(() => "?").join(", ")})`);
11876
+ params.push(...filter.contexts);
11877
+ }
11878
+ if (filter.window) {
11879
+ clauses.push("first_time IS NOT NULL", filter.window.inclusiveEnd ? "first_time <= ?" : "first_time < ?", "last_time >= ?");
11880
+ params.push(filter.window.to, filter.window.from);
11881
+ }
11882
+ if (filter.bbox) {
11883
+ clauses.push("south <= ?", "north >= ?");
11884
+ params.push(filter.bbox.ne[0], filter.bbox.sw[0]);
11885
+ }
11886
+ const where = clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : "";
11887
+ let rows = this.db.prepare(`SELECT * FROM imported_tracks ${where} ORDER BY rowid`).all(...params);
11888
+ const box = filter.bbox;
11889
+ if (box) rows = rows.filter((row) => longitudesOverlap([row.west, row.east], [box.sw[1], box.ne[1]]));
11890
+ return rows.map((row) => this.importFrom(row));
11891
+ }
11892
+ importFrom(row) {
11893
+ const points = this.selectImportedPoints.all(row.id);
11894
+ const segments = [];
11895
+ let current;
11896
+ let currentIndex;
11897
+ for (const { segment, timestamp, lat, lon } of points) {
11898
+ if (segment !== currentIndex) {
11899
+ current = [];
11900
+ segments.push(current);
11901
+ currentIndex = segment;
11902
+ }
11903
+ current.push(timestamp === null ? { position: [lat, lon] } : {
11904
+ position: [lat, lon],
11905
+ timestamp
11906
+ });
11907
+ }
11908
+ return {
11909
+ id: row.id,
11910
+ ...row.context === null ? {} : { context: row.context },
11911
+ ...row.name === null ? {} : { name: row.name },
11912
+ metadata: JSON.parse(row.metadata),
11913
+ segments
11914
+ };
11915
+ }
11603
11916
  /** Worker startup snapshot; queries and name lookups must not open a second database. */
11604
11917
  storedNames() {
11605
11918
  return this.db.prepare("SELECT context, name, timestamp FROM names").all();
@@ -11682,6 +11995,24 @@ function mutate(operation, target) {
11682
11995
  default: return false;
11683
11996
  }
11684
11997
  }
11998
+ /** Writes whose caller waits for their own commit: imports and the recycle bin. */
11999
+ var ACKNOWLEDGED = /* @__PURE__ */ new Set([
12000
+ "storeImport",
12001
+ "binRecorded",
12002
+ "binImport",
12003
+ "restoreFromBin",
12004
+ "purgeFromBin"
12005
+ ]);
12006
+ function acknowledge(operation, target) {
12007
+ switch (operation.method) {
12008
+ case "storeImport": return target.storeImport(...operation.args);
12009
+ case "binRecorded": return target.binRecorded(...operation.args);
12010
+ case "binImport": return target.binImport(...operation.args);
12011
+ case "restoreFromBin": return target.restoreFromBin(...operation.args);
12012
+ case "purgeFromBin": return target.purgeFromBin(...operation.args);
12013
+ default: throw new Error("Unknown track operation");
12014
+ }
12015
+ }
11685
12016
  var isWrite = (operation) => [
11686
12017
  "newPosition",
11687
12018
  "recordName",
@@ -11709,6 +12040,33 @@ if (store) {
11709
12040
  try {
11710
12041
  for (let i = 0; i < operations.length;) {
11711
12042
  const operation = operations[i];
12043
+ if (ACKNOWLEDGED.has(operation.method)) {
12044
+ i++;
12045
+ if (writeFailure) {
12046
+ results.push({
12047
+ id: operation.id,
12048
+ error: writeFailure
12049
+ });
12050
+ continue;
12051
+ }
12052
+ try {
12053
+ let value;
12054
+ target.transaction(() => {
12055
+ value = acknowledge(operation, target);
12056
+ });
12057
+ results.push({
12058
+ id: operation.id,
12059
+ value
12060
+ });
12061
+ } catch (error) {
12062
+ if (error instanceof TransactionStateError) throw error;
12063
+ results.push({
12064
+ id: operation.id,
12065
+ error: errorText(error)
12066
+ });
12067
+ }
12068
+ continue;
12069
+ }
11712
12070
  if (isWrite(operation)) {
11713
12071
  const first = i;
11714
12072
  while (i < operations.length && isWrite(operations[i])) i++;
@@ -11769,6 +12127,18 @@ if (store) {
11769
12127
  case "getFilteredTimedTracks":
11770
12128
  value = await target.getFilteredTimedTracks(operation.args[0], operation.args[1], debug, operation.args[3]);
11771
12129
  break;
12130
+ case "getImport":
12131
+ value = target.getImport(...operation.args);
12132
+ break;
12133
+ case "findImports":
12134
+ value = target.findImports(...operation.args);
12135
+ break;
12136
+ case "binEntries":
12137
+ value = target.binEntries();
12138
+ break;
12139
+ case "deletedSpans":
12140
+ value = target.deletedSpans();
12141
+ break;
11772
12142
  default: throw new Error("Unknown track operation");
11773
12143
  }
11774
12144
  results.push({