pmtiles-swarm 0.32.0 → 0.32.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.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,43 @@
7
7
  ### 🐞 Bug fixes
8
8
  - _...Add new stuff here..._
9
9
 
10
+ ## 0.32.2
11
+ ### ✨ Features and improvements
12
+ - _...Add new stuff here..._
13
+
14
+ ### 🐞 Bug fixes
15
+ - **The head warmer no longer skips every archive it was built for.** It decided an archive was done
16
+ by `Boolean(entry.pmtiles?.format)` — "a summary that names a format is one a header was read
17
+ for". That is not what a format means. A feed carries format, zoom range and bounds precisely so a
18
+ subscriber can judge a 698 GiB download before starting one, so a subscribed archive arrives fully
19
+ summarised before a byte of it exists locally. Every one of them was therefore retired on the spot,
20
+ and retired silently: the runner logged "every archive has been summarised; nothing to warm" and
21
+ meant it. The archives most in need of their header were the only ones never offered one, and the
22
+ visible symptom was a readable TileJSON — served from that same feed summary, touching no bytes —
23
+ beside tile reads that failed and previews that came up empty.
24
+
25
+ Entries now record where the summary came from: `summarySource: 'header'` where an archive's own
26
+ header answered, `'feed'` where a subscription was told. An entry written before this has neither,
27
+ and is treated as unread — the self-healing direction, since a local archive re-reads its own
28
+ header off local disk for nothing, while assuming the opposite would leave every existing
29
+ subscription stuck exactly as it is.
30
+
31
+ - _...Add new stuff here..._
32
+
33
+ ## 0.32.1
34
+ ### ✨ Features and improvements
35
+ - _...Add new stuff here..._
36
+
37
+ ### 🐞 Bug fixes
38
+ - **Requires pmtiles-torrent 0.5.1, so the connection indicator and Recheck files actually work.**
39
+ Both features shipped against a dependency range that still allowed 0.4.6, which has neither
40
+ sidecar op. The declared requirement and the declared dependency disagreed, and the symptom was
41
+ a feature that looked built and did nothing: the indicator hid itself, because an engine that
42
+ cannot answer is deliberately not reported as unreachable, so there was nothing to see and
43
+ nothing to explain why.
44
+
45
+ - _...Add new stuff here..._
46
+
10
47
  ## 0.32.0
11
48
  ### ✨ Features and improvements
12
49
  - _...Add new stuff here..._
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pmtiles-swarm",
3
- "version": "0.32.0",
3
+ "version": "0.32.2",
4
4
  "description": "BitTorrent distribution for PMTiles map archives: create torrents, watch folders, publish and subscribe to RSS feeds, and seed through qBittorrent or an embedded client",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
@@ -46,7 +46,7 @@
46
46
  "maplibre-gl": "^6.2.0",
47
47
  "parse-torrent": "^11.0.24",
48
48
  "pmtiles": "^4.4.1",
49
- "pmtiles-torrent": "^0.4.6",
49
+ "pmtiles-torrent": "^0.5.1",
50
50
  "webtorrent": "^3.0.21"
51
51
  },
52
52
  "engines": {
package/src/api.js CHANGED
@@ -2168,6 +2168,7 @@ export function createApp({
2168
2168
  entry = await catalog.put({
2169
2169
  infoHash: entry.infoHash,
2170
2170
  pmtiles: summary,
2171
+ summarySource: 'header',
2171
2172
  });
2172
2173
  } catch (error) {
2173
2174
  // The content had its say. Record it so this is answered from the
@@ -2383,6 +2384,7 @@ export function createApp({
2383
2384
  entry = await catalog.put({
2384
2385
  infoHash: entry.infoHash,
2385
2386
  pmtiles: summary,
2387
+ summarySource: 'header',
2386
2388
  });
2387
2389
  }
2388
2390
  }
package/src/library.js CHANGED
@@ -204,6 +204,10 @@ export class Library {
204
204
  // The torrent names the file, so the save path is its parent directory.
205
205
  savePath: path.dirname(absolute),
206
206
  pmtiles: summary,
207
+ // Read out of the archive, not taken from anybody's word for it. The
208
+ // prewarmer needs the difference: a summary that arrived in a feed says
209
+ // nothing about whether the header is on this disk. See prewarm.due().
210
+ summarySource: 'header',
207
211
  kind: identified.kind,
208
212
  sparse: options.sparse,
209
213
  md5: created.md5,
@@ -747,6 +751,10 @@ export class Library {
747
751
  buildDate: options.buildDate,
748
752
  savePath,
749
753
  pmtiles: summary,
754
+ // Read out of the archive, not taken from anybody's word for it. The
755
+ // prewarmer needs the difference: a summary that arrived in a feed says
756
+ // nothing about whether the header is on this disk. See prewarm.due().
757
+ summarySource: 'header',
750
758
  kind: identified.kind,
751
759
  sparse: options.sparse,
752
760
  md5: created.md5,
@@ -1212,6 +1220,10 @@ export class Library {
1212
1220
  magnet,
1213
1221
  webSeeds: [],
1214
1222
  pmtiles: summary,
1223
+ // Read out of the archive, not taken from anybody's word for it. The
1224
+ // prewarmer needs the difference: a summary that arrived in a feed says
1225
+ // nothing about whether the header is on this disk. See prewarm.due().
1226
+ summarySource: 'header',
1215
1227
  kind: guessKind(torrent.name ?? ''),
1216
1228
  mode: 'mirror',
1217
1229
  // The engine's own account of it. It is on disk under its real name
package/src/prewarm.js CHANGED
@@ -157,12 +157,23 @@ export class HeadWarmer {
157
157
  const kind = entry.kind ?? guessKind(entry.name ?? '');
158
158
  if (kind !== 'pmtiles') return false;
159
159
 
160
- // A summary that names a format is one a header was actually read for.
161
- // Anything else an empty object, or one left behind by a read that raced
162
- // its deadline — is not an answer, and treating it as one retired the
163
- // archive permanently: no logs, no retries, nothing to explain the silence.
160
+ // A summary is only an answer about *this disk* if a header on this disk
161
+ // produced it. `format` was standing in for that and does not mean it.
162
+ //
163
+ // The feed carries format, zoom range and bounds precisely so a subscriber
164
+ // can judge a 698 GiB download before starting one -- so a subscribed
165
+ // archive arrives fully summarised, before a byte of it exists here. Read
166
+ // as "a header was read", that retired every subscribed archive on the spot
167
+ // and did it silently, since as far as this was concerned there was nothing
168
+ // to do. The archives most in need of their header were the only ones never
169
+ // offered one, and the log said "nothing to warm".
170
+ //
171
+ // An entry from before this was recorded has no source, and is treated as
172
+ // unread. That is the self-healing direction: a local archive re-reads its
173
+ // own header off local disk and costs nothing, where the other way round
174
+ // leaves every existing subscription stuck exactly as it was.
164
175
  const summary = entry.pmtiles;
165
- const read = Boolean(summary?.format);
176
+ const read = entry.summarySource === 'header' && Boolean(summary?.format);
166
177
  if (read && (summary.format !== 'pbf' || summary.vectorLayers)) {
167
178
  return false;
168
179
  }
@@ -257,6 +268,9 @@ export class HeadWarmer {
257
268
  const stored = await this.#catalog.put({
258
269
  infoHash: entry.infoHash,
259
270
  pmtiles: { ...entry.pmtiles, ...summary },
271
+ // The whole point of this pass: from here on the entry's summary is
272
+ // one the header answered for, so due() stops choosing it.
273
+ summarySource: 'header',
260
274
  });
261
275
  // Said accurately, because the two halves arrive separately and the
262
276
  // difference matters: the header is at byte zero, while the JSON
@@ -419,6 +419,10 @@ export class SubscriptionManager {
419
419
  // with what the archive itself says; this only means the wait is not
420
420
  // spent unservable.
421
421
  pmtiles: item.pmtiles,
422
+ // Told, not read. The feed carries this so a subscriber can judge a
423
+ // 698 GiB download before starting one, which means it is populated
424
+ // before a single byte exists here.
425
+ summarySource: 'feed',
422
426
  };
423
427
 
424
428
  // The .torrent is preferred where there is one: it carries the trackers