@mapvx/web-js 2.1.0-dev.1 → 3.0.0-dev.2

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.
Files changed (51) hide show
  1. package/dist/cjs/controllers/routeController.js +1 -4
  2. package/dist/cjs/controllers/routeController.js.map +1 -1
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/infrastructure/cache/CacheManager.js.map +1 -1
  5. package/dist/cjs/logger/logger.js +2 -3
  6. package/dist/cjs/logger/logger.js.map +1 -1
  7. package/dist/cjs/logger/sentry.js +1 -1
  8. package/dist/cjs/map/map.js +108 -49
  9. package/dist/cjs/map/map.js.map +1 -1
  10. package/dist/cjs/repository/repository.js +21 -21
  11. package/dist/cjs/repository/repository.js.map +1 -1
  12. package/dist/cjs/sdk.js +21 -21
  13. package/dist/cjs/sdk.js.map +1 -1
  14. package/dist/cjs/utils/opening-hours-helper.js +22 -19
  15. package/dist/cjs/utils/opening-hours-helper.js.map +1 -1
  16. package/dist/cjs/utils/semaphore.js.map +1 -1
  17. package/dist/es/controllers/routeController.d.ts.map +1 -1
  18. package/dist/es/controllers/routeController.js +1 -4
  19. package/dist/es/controllers/routeController.js.map +1 -1
  20. package/dist/es/domain/models/route.d.ts +1 -1
  21. package/dist/es/domain/models/route.d.ts.map +1 -1
  22. package/dist/es/index.d.ts +1 -1
  23. package/dist/es/index.d.ts.map +1 -1
  24. package/dist/es/index.js.map +1 -1
  25. package/dist/es/infrastructure/cache/CacheManager.d.ts.map +1 -1
  26. package/dist/es/infrastructure/cache/CacheManager.js.map +1 -1
  27. package/dist/es/logger/logger.d.ts.map +1 -1
  28. package/dist/es/logger/logger.js +2 -3
  29. package/dist/es/logger/logger.js.map +1 -1
  30. package/dist/es/logger/sentry.js +1 -1
  31. package/dist/es/map/map.d.ts +11 -5
  32. package/dist/es/map/map.d.ts.map +1 -1
  33. package/dist/es/map/map.js +108 -49
  34. package/dist/es/map/map.js.map +1 -1
  35. package/dist/es/repository/repository.js +21 -21
  36. package/dist/es/repository/repository.js.map +1 -1
  37. package/dist/es/sdk.d.ts +26 -1
  38. package/dist/es/sdk.d.ts.map +1 -1
  39. package/dist/es/sdk.js +21 -21
  40. package/dist/es/sdk.js.map +1 -1
  41. package/dist/es/utils/opening-hours-helper.d.ts.map +1 -1
  42. package/dist/es/utils/opening-hours-helper.js +22 -19
  43. package/dist/es/utils/opening-hours-helper.js.map +1 -1
  44. package/dist/es/utils/semaphore.d.ts +1 -1
  45. package/dist/es/utils/semaphore.d.ts.map +1 -1
  46. package/dist/es/utils/semaphore.js.map +1 -1
  47. package/dist/umd/index.js +309 -134
  48. package/dist/umd/index.js.map +1 -1
  49. package/dist/umd/styles.css +0 -12
  50. package/dist/umd/styles.css.map +1 -1
  51. package/package.json +1 -1
@@ -15,6 +15,18 @@ const CIRCLE_LAYER_SUFFIXES = {
15
15
  belowLabels: "-below-labels",
16
16
  top: "-top",
17
17
  };
18
+ /**
19
+ * How many times a failed vector tile is reloaded before it is dropped from the retry
20
+ * queue. Bounded so a permanently unavailable tile stops being requested, while a tile
21
+ * that failed on a transient network error still gets several chances.
22
+ */
23
+ const MAX_TILE_RETRIES = 5;
24
+ /**
25
+ * Base delay for the retry backoff. Attempt N waits `BASE * 2^(N-1)` before it is
26
+ * eligible again — 1s, 2s, 4s, 8s — so a tile that keeps failing is retried ever less
27
+ * often instead of once per timer tick.
28
+ */
29
+ const TILE_RETRY_BASE_DELAY_MS = 1000;
18
30
  /** Fill/line layer ids for one circle render-order bucket. */
19
31
  function circleLayerIdsFor(order) {
20
32
  const suffix = CIRCLE_LAYER_SUFFIXES[order];
@@ -94,7 +106,7 @@ function parsePaddingValue(value, dimension) {
94
106
  if (!match) {
95
107
  return 0;
96
108
  }
97
- const numericValue = parseFloat(match[1]);
109
+ const numericValue = Number.parseFloat(match[1]);
98
110
  const unit = match[2] || "px";
99
111
  switch (unit) {
100
112
  case "px":
@@ -177,6 +189,12 @@ export class InternalMapVXMap extends Loggeable {
177
189
  this.enableHover = false;
178
190
  this.hoveredId = "unselected";
179
191
  this.failedTiles = new Set();
192
+ /**
193
+ * Retry bookkeeping per failed tile. A WeakMap so it can never outlive the tiles
194
+ * themselves — entries disappear with the tile objects, which is why none of the
195
+ * `failedTiles.clear()` paths have to reset it.
196
+ */
197
+ this.tileRetryState = new WeakMap();
180
198
  this.geoLocation = navigator.geolocation;
181
199
  /** Owns the deck.gl overlay + GLB scene; created on style load only when 3D is active. */
182
200
  this.scene3D = undefined;
@@ -295,7 +313,7 @@ export class InternalMapVXMap extends Loggeable {
295
313
  .catch(console.error);
296
314
  }
297
315
  onMapStyleLoaded(mapConfig, container, style) {
298
- var _a, _b, _c, _d, _e;
316
+ var _a, _b, _c, _d, _e, _f, _g;
299
317
  // Determine if service worker caching should be enabled
300
318
  const useServiceWorkerCaching = this.tileCacheConfig.enabled && this.tileCacheConfig.persistToServiceWorker;
301
319
  if (useServiceWorkerCaching) {
@@ -326,9 +344,9 @@ export class InternalMapVXMap extends Loggeable {
326
344
  (_d = (_c = this.map.touchZoomRotate) === null || _c === void 0 ? void 0 : _c.disableRotation) === null || _d === void 0 ? void 0 : _d.call(_c);
327
345
  }
328
346
  this.map.addControl(new NavigationControl({
329
- showCompass: mapConfig.showCompass !== undefined ? mapConfig.showCompass : true,
330
- showZoom: mapConfig.showZoom !== undefined ? mapConfig.showZoom : true,
331
- }), (_e = mapConfig.navigationPosition) !== null && _e !== void 0 ? _e : "top-right");
347
+ showCompass: (_e = mapConfig.showCompass) !== null && _e !== void 0 ? _e : true,
348
+ showZoom: (_f = mapConfig.showZoom) !== null && _f !== void 0 ? _f : true,
349
+ }), (_g = mapConfig.navigationPosition) !== null && _g !== void 0 ? _g : "top-right");
332
350
  this.map.on("load", () => {
333
351
  var _a, _b;
334
352
  // 3D libraries load asynchronously; the rest of the load flow does not wait on them.
@@ -429,7 +447,7 @@ export class InternalMapVXMap extends Loggeable {
429
447
  ]);
430
448
  }
431
449
  catch (error) {
432
- // Layer language update may fail if layer doesn't exist
450
+ this.debugLog("Layer language update failed (layer may not exist)", error);
433
451
  }
434
452
  });
435
453
  // indoor layers
@@ -453,7 +471,7 @@ export class InternalMapVXMap extends Loggeable {
453
471
  ]);
454
472
  }
455
473
  catch (error) {
456
- // Layer language update may fail if layer doesn't exist
474
+ this.debugLog("Layer language update failed (layer may not exist)", error);
457
475
  }
458
476
  });
459
477
  }
@@ -484,7 +502,7 @@ export class InternalMapVXMap extends Loggeable {
484
502
  this.logEvent("clearTileCache");
485
503
  }
486
504
  catch (error) {
487
- // Tile cache clear may fail if cache doesn't exist
505
+ this.debugLog("Tile cache clear failed (cache may not exist)", error);
488
506
  }
489
507
  }
490
508
  /**
@@ -573,7 +591,7 @@ export class InternalMapVXMap extends Loggeable {
573
591
  (_d = options === null || options === void 0 ? void 0 : options.onComplete) === null || _d === void 0 ? void 0 : _d.call(options);
574
592
  }
575
593
  catch (error) {
576
- // Parent place/floor update may fail
594
+ this.debugLog("Parent place/floor update failed", error);
577
595
  }
578
596
  }
579
597
  setLang(lang) {
@@ -641,7 +659,7 @@ export class InternalMapVXMap extends Loggeable {
641
659
  return markerCreated.id;
642
660
  }
643
661
  catch (error) {
644
- throw new Error("Failed to add marker");
662
+ throw new Error("Failed to add marker", { cause: error });
645
663
  }
646
664
  }
647
665
  updateMarker(markerConfig) {
@@ -655,7 +673,7 @@ export class InternalMapVXMap extends Loggeable {
655
673
  return (_a = marker === null || marker === void 0 ? void 0 : marker.id) !== null && _a !== void 0 ? _a : "";
656
674
  }
657
675
  catch (error) {
658
- throw new Error("Failed to update marker");
676
+ throw new Error("Failed to update marker", { cause: error });
659
677
  }
660
678
  }
661
679
  updateMarkerPosition(markerId, newPosition) {
@@ -667,7 +685,7 @@ export class InternalMapVXMap extends Loggeable {
667
685
  this.logEvent("updateMarkerPosition");
668
686
  }
669
687
  catch (error) {
670
- throw new Error("Failed to update marker position");
688
+ throw new Error("Failed to update marker position", { cause: error });
671
689
  }
672
690
  }
673
691
  showMarker(markerId) {
@@ -679,7 +697,7 @@ export class InternalMapVXMap extends Loggeable {
679
697
  this.logEvent("showMarker");
680
698
  }
681
699
  catch (error) {
682
- // Show marker may fail if marker doesn't exist
700
+ this.debugLog("Show marker failed (marker may not exist)", error);
683
701
  }
684
702
  }
685
703
  hideMarker(markerId) {
@@ -691,7 +709,7 @@ export class InternalMapVXMap extends Loggeable {
691
709
  this.logEvent("hideMarker");
692
710
  }
693
711
  catch (error) {
694
- // Hide marker may fail if marker doesn't exist
712
+ this.debugLog("Hide marker failed (marker may not exist)", error);
695
713
  }
696
714
  }
697
715
  removeMarker(markerId) {
@@ -704,7 +722,7 @@ export class InternalMapVXMap extends Loggeable {
704
722
  this.logEvent("removeMarker");
705
723
  }
706
724
  catch (error) {
707
- throw new Error("Failed to remove marker");
725
+ throw new Error("Failed to remove marker", { cause: error });
708
726
  }
709
727
  }
710
728
  removeAllMarkers() {
@@ -716,7 +734,7 @@ export class InternalMapVXMap extends Loggeable {
716
734
  this.logEvent("removeAllMarkers");
717
735
  }
718
736
  catch (error) {
719
- throw new Error("Failed to remove all markers");
737
+ throw new Error("Failed to remove all markers", { cause: error });
720
738
  }
721
739
  }
722
740
  addCircle(circle) {
@@ -825,7 +843,7 @@ export class InternalMapVXMap extends Loggeable {
825
843
  this.logEvent("removeCircle");
826
844
  }
827
845
  catch (error) {
828
- throw new Error("Failed to remove circle");
846
+ throw new Error("Failed to remove circle", { cause: error });
829
847
  }
830
848
  }
831
849
  removeAllCircles() {
@@ -835,7 +853,7 @@ export class InternalMapVXMap extends Loggeable {
835
853
  this.logEvent("removeAllCircles");
836
854
  }
837
855
  catch (error) {
838
- throw new Error("Failed to remove all circles");
856
+ throw new Error("Failed to remove all circles", { cause: error });
839
857
  }
840
858
  }
841
859
  showCircle(circleId) {
@@ -850,6 +868,7 @@ export class InternalMapVXMap extends Loggeable {
850
868
  return true;
851
869
  }
852
870
  catch (error) {
871
+ this.debugLog("Show circle failed", error);
853
872
  return false;
854
873
  }
855
874
  }
@@ -865,6 +884,7 @@ export class InternalMapVXMap extends Loggeable {
865
884
  return true;
866
885
  }
867
886
  catch (error) {
887
+ this.debugLog("Hide circle failed", error);
868
888
  return false;
869
889
  }
870
890
  }
@@ -1011,7 +1031,7 @@ export class InternalMapVXMap extends Loggeable {
1011
1031
  }
1012
1032
  }
1013
1033
  catch (error) {
1014
- // Style may not be loaded yet; the styledata listener re-adds the layers
1034
+ this.debugLog("Layers not added: style not loaded yet, the styledata listener will re-add them", error);
1015
1035
  }
1016
1036
  }
1017
1037
  /**
@@ -1030,7 +1050,7 @@ export class InternalMapVXMap extends Loggeable {
1030
1050
  source === null || source === void 0 ? void 0 : source.setData(this.circleFeatureCollection());
1031
1051
  }
1032
1052
  catch (error) {
1033
- // Source may not exist while a new style is loading
1053
+ this.debugLog("Source not found (a new style may still be loading)", error);
1034
1054
  }
1035
1055
  }
1036
1056
  /**
@@ -1060,7 +1080,7 @@ export class InternalMapVXMap extends Loggeable {
1060
1080
  }
1061
1081
  }
1062
1082
  catch (error) {
1063
- // Resize may fail if the map/container is not ready
1083
+ this.debugLog("Resize failed (map/container may not be ready)", error);
1064
1084
  }
1065
1085
  }
1066
1086
  updateMarkersTo(floorId) {
@@ -1221,6 +1241,7 @@ export class InternalMapVXMap extends Loggeable {
1221
1241
  (_a = options === null || options === void 0 ? void 0 : options.onComplete) === null || _a === void 0 ? void 0 : _a.call(options);
1222
1242
  }
1223
1243
  fitCoordinates(coordinates, options) {
1244
+ var _a, _b;
1224
1245
  if (coordinates.length === 0) {
1225
1246
  return;
1226
1247
  }
@@ -1262,8 +1283,8 @@ export class InternalMapVXMap extends Loggeable {
1262
1283
  const fitOptions = Object.assign(Object.assign({}, options), { padding });
1263
1284
  const bearing = this.map.getBearing();
1264
1285
  const pitch = this.map.getPitch();
1265
- fitOptions.bearing = fitOptions.bearing !== undefined ? fitOptions.bearing : bearing;
1266
- fitOptions.pitch = fitOptions.pitch !== undefined ? fitOptions.pitch : pitch;
1286
+ (_a = fitOptions.bearing) !== null && _a !== void 0 ? _a : (fitOptions.bearing = bearing);
1287
+ (_b = fitOptions.pitch) !== null && _b !== void 0 ? _b : (fitOptions.pitch = pitch);
1267
1288
  if (onComplete) {
1268
1289
  void this.map.once("moveend", onComplete);
1269
1290
  }
@@ -1271,7 +1292,7 @@ export class InternalMapVXMap extends Loggeable {
1271
1292
  }
1272
1293
  }
1273
1294
  easeToCoordinates(coordinates, options) {
1274
- var _a;
1295
+ var _a, _b, _c;
1275
1296
  if (coordinates.length === 0) {
1276
1297
  return;
1277
1298
  }
@@ -1290,14 +1311,14 @@ export class InternalMapVXMap extends Loggeable {
1290
1311
  this.map.easeTo(Object.assign(Object.assign({}, options), { center: { lat: coord.lat, lng: coord.lng }, padding }));
1291
1312
  return;
1292
1313
  }
1293
- const bearing = (options === null || options === void 0 ? void 0 : options.bearing) !== undefined ? options.bearing : this.map.getBearing();
1294
- const pitch = (options === null || options === void 0 ? void 0 : options.pitch) !== undefined ? options.pitch : this.map.getPitch();
1314
+ const bearing = (_a = options === null || options === void 0 ? void 0 : options.bearing) !== null && _a !== void 0 ? _a : this.map.getBearing();
1315
+ const pitch = (_b = options === null || options === void 0 ? void 0 : options.pitch) !== null && _b !== void 0 ? _b : this.map.getPitch();
1295
1316
  const cameraParams = {
1296
1317
  bearing,
1297
1318
  padding,
1298
1319
  containerWidth,
1299
1320
  containerHeight,
1300
- maxZoom: (_a = options === null || options === void 0 ? void 0 : options.maxZoom) !== null && _a !== void 0 ? _a : this.map.getMaxZoom(),
1321
+ maxZoom: (_c = options === null || options === void 0 ? void 0 : options.maxZoom) !== null && _c !== void 0 ? _c : this.map.getMaxZoom(),
1301
1322
  minZoom: this.map.getMinZoom(),
1302
1323
  };
1303
1324
  // Orthographic oriented fit (exact for pitch 0). maplibre's fitBounds only
@@ -1341,7 +1362,7 @@ export class InternalMapVXMap extends Loggeable {
1341
1362
  (_c = options === null || options === void 0 ? void 0 : options.onComplete) === null || _c === void 0 ? void 0 : _c.call(options);
1342
1363
  }
1343
1364
  catch (error) {
1344
- // Floor update may fail
1365
+ this.debugLog("Floor update failed", error);
1345
1366
  }
1346
1367
  }
1347
1368
  updatePlaceAndFloorForRouting(placeId, floorId) {
@@ -1354,7 +1375,7 @@ export class InternalMapVXMap extends Loggeable {
1354
1375
  this.logEvent("updatePlaceAndFloorForRouting", { floorId });
1355
1376
  }
1356
1377
  catch (error) {
1357
- // Place/floor update for routing may fail
1378
+ this.debugLog("Place/floor update for routing failed", error);
1358
1379
  }
1359
1380
  }
1360
1381
  async addRoute(config, drawConfig = {}) {
@@ -1367,7 +1388,7 @@ export class InternalMapVXMap extends Loggeable {
1367
1388
  return route;
1368
1389
  }
1369
1390
  catch (error) {
1370
- throw new Error("Error: Failed to add route");
1391
+ throw new Error("Error: Failed to add route", { cause: error });
1371
1392
  }
1372
1393
  }
1373
1394
  async addLiveRoute(config, drawConfig = {}) {
@@ -1404,8 +1425,8 @@ export class InternalMapVXMap extends Loggeable {
1404
1425
  return route;
1405
1426
  }
1406
1427
  catch (error) {
1407
- const cause = error instanceof Error ? error.message : String(error);
1408
- throw new Error(`Error: Failed to add route: ${cause}`);
1428
+ const reason = error instanceof Error ? error.message : String(error);
1429
+ throw new Error(`Error: Failed to add route: ${reason}`, { cause: error });
1409
1430
  }
1410
1431
  }
1411
1432
  updateRouteProgress(routeId, position, behindStyle = { type: "Solid", color: "#276EF1" }) {
@@ -1416,7 +1437,7 @@ export class InternalMapVXMap extends Loggeable {
1416
1437
  this.routeController.updateRouteProgress(routeId, position, behindConfig);
1417
1438
  }
1418
1439
  catch (error) {
1419
- // Update may fail if route doesn't exist
1440
+ this.debugLog("Route update failed (route may not exist)", error);
1420
1441
  }
1421
1442
  }
1422
1443
  removeRoute(routeId) {
@@ -1425,7 +1446,7 @@ export class InternalMapVXMap extends Loggeable {
1425
1446
  this.routeController.removeRoute(routeId);
1426
1447
  }
1427
1448
  catch (error) {
1428
- // Remove route may fail if no route exists
1449
+ this.debugLog("Remove route failed (no route may exist)", error);
1429
1450
  }
1430
1451
  this.syncLiveRouteId();
1431
1452
  }
@@ -1435,7 +1456,7 @@ export class InternalMapVXMap extends Loggeable {
1435
1456
  this.routeController.removeRoute(routeId);
1436
1457
  }
1437
1458
  catch (error) {
1438
- // Remove route may fail if route doesn't exist
1459
+ this.debugLog("Remove route failed (route may not exist)", error);
1439
1460
  }
1440
1461
  this.syncLiveRouteId();
1441
1462
  }
@@ -1445,7 +1466,7 @@ export class InternalMapVXMap extends Loggeable {
1445
1466
  this.routeController.removeAllRoutes();
1446
1467
  }
1447
1468
  catch (error) {
1448
- // Remove all routes may fail
1469
+ this.debugLog("Remove all routes failed", error);
1449
1470
  }
1450
1471
  this.syncLiveRouteId();
1451
1472
  }
@@ -1585,7 +1606,7 @@ export class InternalMapVXMap extends Loggeable {
1585
1606
  }
1586
1607
  }
1587
1608
  catch (error) {
1588
- // Place selection may fail if layer doesn't exist
1609
+ this.debugLog("Place selection failed (layer may not exist)", error);
1589
1610
  }
1590
1611
  }
1591
1612
  is3DStyle() {
@@ -1674,7 +1695,7 @@ export class InternalMapVXMap extends Loggeable {
1674
1695
  return route;
1675
1696
  }
1676
1697
  catch (error) {
1677
- throw new Error("Error: Failed to start animating route");
1698
+ throw new Error("Error: Failed to start animating route", { cause: error });
1678
1699
  }
1679
1700
  }
1680
1701
  async startAnimateRouteV2(routeConfig, drawConfig = {}, animationConfig = {}) {
@@ -1693,7 +1714,7 @@ export class InternalMapVXMap extends Loggeable {
1693
1714
  return route;
1694
1715
  }
1695
1716
  catch (error) {
1696
- throw new Error("Error: Failed to start animating route");
1717
+ throw new Error("Error: Failed to start animating route", { cause: error });
1697
1718
  }
1698
1719
  }
1699
1720
  pauseAnimateRoute(routeId) {
@@ -1702,7 +1723,7 @@ export class InternalMapVXMap extends Loggeable {
1702
1723
  this.logEvent("pauseAnimateRoute");
1703
1724
  }
1704
1725
  catch (error) {
1705
- // Pause animation may fail if no animation is running
1726
+ this.debugLog("Pause animation failed (no animation may be running)", error);
1706
1727
  }
1707
1728
  }
1708
1729
  resumeAnimateRoute(routeId) {
@@ -1711,7 +1732,7 @@ export class InternalMapVXMap extends Loggeable {
1711
1732
  this.logEvent("resumeAnimateRoute");
1712
1733
  }
1713
1734
  catch (error) {
1714
- // Resume animation may fail if no animation is paused
1735
+ this.debugLog("Resume animation failed (no animation may be paused)", error);
1715
1736
  }
1716
1737
  }
1717
1738
  restartAnimateRoute(routeId) {
@@ -1720,7 +1741,7 @@ export class InternalMapVXMap extends Loggeable {
1720
1741
  this.logEvent("restartAnimateRoute");
1721
1742
  }
1722
1743
  catch (error) {
1723
- // Restart animation may fail if no animation exists
1744
+ this.debugLog("Restart animation failed (no animation may exist)", error);
1724
1745
  }
1725
1746
  }
1726
1747
  stopAnimateRoute() {
@@ -1745,7 +1766,7 @@ export class InternalMapVXMap extends Loggeable {
1745
1766
  return route;
1746
1767
  }
1747
1768
  catch (error) {
1748
- throw new Error("Error: Failed to add step animation");
1769
+ throw new Error("Error: Failed to add step animation", { cause: error });
1749
1770
  }
1750
1771
  }
1751
1772
  async addStepAnimationV2(routeConfig, drawConfig = {}, animationConfig = {}) {
@@ -1765,7 +1786,7 @@ export class InternalMapVXMap extends Loggeable {
1765
1786
  return route;
1766
1787
  }
1767
1788
  catch (error) {
1768
- throw new Error("Error: Failed to add step animation");
1789
+ throw new Error("Error: Failed to add step animation", { cause: error });
1769
1790
  }
1770
1791
  }
1771
1792
  nextStepAnimation(routeId) {
@@ -1774,7 +1795,7 @@ export class InternalMapVXMap extends Loggeable {
1774
1795
  this.logEvent("nextStepAnimation");
1775
1796
  }
1776
1797
  catch (error) {
1777
- throw new Error("Error: Failed to animate step");
1798
+ throw new Error("Error: Failed to animate step", { cause: error });
1778
1799
  }
1779
1800
  }
1780
1801
  previousStepAnimation(routeId) {
@@ -1863,7 +1884,7 @@ export class InternalMapVXMap extends Loggeable {
1863
1884
  }
1864
1885
  }
1865
1886
  catch (error) {
1866
- throw new Error("Failed to add border to places");
1887
+ throw new Error("Failed to add border to places", { cause: error });
1867
1888
  }
1868
1889
  }
1869
1890
  createBorderedPlacesLayer() {
@@ -1966,16 +1987,54 @@ export class InternalMapVXMap extends Loggeable {
1966
1987
  // Remove invalid tiles from the set
1967
1988
  tilesToRemove.forEach((tile) => this.failedTiles.delete(tile));
1968
1989
  // Attempt to reload valid failed tiles
1990
+ const now = Date.now();
1969
1991
  tilesToRetry.forEach((tile) => {
1992
+ var _a;
1993
+ const retry = (_a = this.tileRetryState.get(tile)) !== null && _a !== void 0 ? _a : {
1994
+ attempts: 0,
1995
+ nextAttemptAt: 0,
1996
+ pending: false,
1997
+ };
1998
+ // Skip tiles with a reload still in flight or still inside their backoff window.
1999
+ if (retry.pending || now < retry.nextAttemptAt)
2000
+ return;
2001
+ const onFailure = (error) => {
2002
+ const attempts = retry.attempts + 1;
2003
+ if (attempts >= MAX_TILE_RETRIES) {
2004
+ this.debugLog(`Tile reload failed ${attempts} times, dropping it from the retry queue`, error);
2005
+ this.failedTiles.delete(tile);
2006
+ this.tileRetryState.delete(tile);
2007
+ return;
2008
+ }
2009
+ // Stay in the queue and try again once the backoff has elapsed.
2010
+ const backoffMs = TILE_RETRY_BASE_DELAY_MS * 2 ** (attempts - 1);
2011
+ this.debugLog(`Tile reload failed (attempt ${attempts}/${MAX_TILE_RETRIES}), retrying in ${backoffMs}ms`, error);
2012
+ this.tileRetryState.set(tile, {
2013
+ attempts,
2014
+ nextAttemptAt: Date.now() + backoffMs,
2015
+ pending: false,
2016
+ });
2017
+ };
1970
2018
  try {
1971
2019
  // Verify tile still exists and is in a retryable state
1972
2020
  if (tile && (tile.state === "errored" || tile.state === "expired")) {
1973
- source.loadTile(tile);
2021
+ this.tileRetryState.set(tile, Object.assign(Object.assign({}, retry), { pending: true }));
2022
+ // loadTile is async, so a rejection escapes this try/catch and has to be handled
2023
+ // on the promise itself - otherwise it surfaces as an unhandled rejection and the
2024
+ // tile is retried on every tick regardless of how often it has already failed.
2025
+ source
2026
+ .loadTile(tile)
2027
+ .then(() => {
2028
+ // Loaded: forget the attempt history so a later failure starts from zero. The
2029
+ // tile leaves the queue through the tilesToRemove branch on the next tick,
2030
+ // once its state is no longer errored/expired.
2031
+ this.tileRetryState.delete(tile);
2032
+ })
2033
+ .catch(onFailure);
1974
2034
  }
1975
2035
  }
1976
2036
  catch (error) {
1977
- // If reload fails, remove tile from retry queue - this is expected
1978
- this.failedTiles.delete(tile);
2037
+ onFailure(error);
1979
2038
  }
1980
2039
  });
1981
2040
  }, 1000);