sailkick-boat 0.14.4 → 0.14.5

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/README.md CHANGED
@@ -63,10 +63,19 @@ comes from the cloud **announcing bakes**, not from a clock:
63
63
  `X-Sailkick-Cache` reports `HIT` / `MISS` / `UPDATED` / `STALE` / `LIVE` per response.
64
64
 
65
65
  **Static vs dynamic — two strategies.** Tiles and app assets are **cache-first**
66
- (pinned, offline forever). Dynamic `/api/*` data (AIS, weather) is **network-first**:
67
- fetched live every time online (so AIS/weather never go stale), with the last
68
- response kept only as an **offline fallback** (`STALE`). Local `/api/history` and the
69
- patched `/api/config` are served specially (above).
66
+ (pinned, offline forever). Dynamic `/api/*` data (AIS, weather, lightning) is
67
+ **network-first**: fetched live every time online (so it never goes stale), with the
68
+ last response kept only as an **offline fallback** (`STALE`). Local `/api/history` and
69
+ the patched `/api/config` are served specially (above).
70
+
71
+ **Exception — velocity tiles are pinned.** The app serves its wind/current field from
72
+ `/api/velocity/tiles/<layer>/<runId>/<z>/<x>/<y>/<hour>.f32`. The forecast **run id is
73
+ in the path**, so a URL's bytes never change — a new run means new URLs. Those are
74
+ cache-first like map tiles: each tile downloads once instead of on every pan, and the
75
+ wind particles + storm field keep rendering **offline**. The velocity *manifest*
76
+ (`?run=latest`) stays network-first, or the boat would pin itself to a stale run
77
+ forever. Nothing prefetches velocity tiles yet, so offline wind covers only what you
78
+ have panned over, and old runs are not pruned.
70
79
 
71
80
  **Offline circuit breaker.** Some boat routers return errors (or hang) for outbound
72
81
  requests when the uplink is down, instead of failing cleanly. Once a fetch fails, the
@@ -18,6 +18,20 @@ const { countBboxTiles, bboxTiles, boxAround } = require('./tiles')
18
18
  // get its live data from the boat's local SignalK and its charts/weather cached.
19
19
  // Also exposes the auth'd /plugins/sailkick-boat/p/* router handlers.
20
20
 
21
+ // Everything under /api/ is dynamic and network-first — EXCEPT paths whose URL is
22
+ // immutable by construction. Velocity tiles embed the forecast run id
23
+ // (/api/velocity/tiles/<layer>/<runId>/<z>/<x>/<y>/<hour>.f32, see the app's
24
+ // public/engine/velocity-client.js), so a given URL's bytes never change: a new
25
+ // forecast run produces new URLs. Treating them as network-first re-downloaded the
26
+ // whole wind field on every pan while online, and left nothing usable offline —
27
+ // pinning them is what makes wind particles and the storm field work with no uplink.
28
+ //
29
+ // The velocity *manifest* is deliberately NOT in here: it resolves `run=latest` and
30
+ // must stay live, otherwise the boat pins itself to a stale forecast run forever.
31
+ const IMMUTABLE_API_PREFIXES = ['/api/velocity/tiles/']
32
+ const isImmutableApi = (p) => IMMUTABLE_API_PREFIXES.some((x) => p.startsWith(x))
33
+ const isNetworkFirst = (p) => p.startsWith('/api/') && !isImmutableApi(p)
34
+
21
35
  function createProxy (app, options) {
22
36
  const log = (m) => (app.debug ? app.debug('[proxy] ' + m) : console.log('[sailkick-boat:proxy]', m))
23
37
  let cfg = null
@@ -138,8 +152,10 @@ function createProxy (app, options) {
138
152
  if (req.method === 'GET' || req.method === 'HEAD') {
139
153
  const { buffer, contentType, cacheState } = await getResource({
140
154
  storeDir: cfg.storeDir, upstream: cfg.upstream, reqPath: req.url, timeoutMs: cfg.timeoutMs,
141
- invalidatedAt: manifest ? manifest.invalidatedAtFor(req.url) : 0,
142
- networkFirst: req.url.startsWith('/api/'), // live data (AIS, weather): fresh online, cache-fallback offline
155
+ // An immutable URL needs no invalidation: a bake announcement can't change
156
+ // bytes that are keyed by run id, so don't let an app rebuild churn them.
157
+ invalidatedAt: isImmutableApi(req.url) ? 0 : (manifest ? manifest.invalidatedAtFor(req.url) : 0),
158
+ networkFirst: isNetworkFirst(req.url), // live data (AIS, weather, lightning): fresh online, cache-fallback offline
143
159
  health
144
160
  })
145
161
  res.setHeader('Content-Type', contentType)
@@ -242,8 +258,8 @@ function createProxy (app, options) {
242
258
  try {
243
259
  const { buffer, contentType, cacheState } = await getResource({
244
260
  storeDir: cfg.storeDir, upstream: cfg.upstream, reqPath, timeoutMs: cfg.timeoutMs,
245
- invalidatedAt: manifest ? manifest.invalidatedAtFor(reqPath) : 0,
246
- networkFirst: reqPath.startsWith('/api/'), health
261
+ invalidatedAt: isImmutableApi(reqPath) ? 0 : (manifest ? manifest.invalidatedAtFor(reqPath) : 0),
262
+ networkFirst: isNetworkFirst(reqPath), health
247
263
  })
248
264
  res.set('Content-Type', contentType)
249
265
  res.set('X-Sailkick-Cache', cacheState)
@@ -390,4 +406,4 @@ function createProxy (app, options) {
390
406
  return { start, stop, status, handleGet, handlePrefetch, handlePrefetchRegion, handleClear, _serveMirror: serveMirror, _serveConfig: serveConfig, _manifest: () => manifest, _seeder: () => seeder, _startAreaPrefetch: startAreaPrefetch, _area: () => area }
391
407
  }
392
408
 
393
- module.exports = { createProxy }
409
+ module.exports = { createProxy, isNetworkFirst, isImmutableApi }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sailkick-boat",
3
- "version": "0.14.4",
3
+ "version": "0.14.5",
4
4
  "description": "EARLY ALPHA — cloud telemetry + offline maps for sailkick boats (www.sailkick.io; register on the web, paste the write token). Gapless boat→cloud telemetry sync to InfluxDB, and a local proxy that keeps the sailkick app and its charts/maps working fully offline on board.",
5
5
  "main": "index.js",
6
6
  "scripts": {