@signalk/tracks-plugin 3.0.0-beta.1 → 3.0.0-beta.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.
@@ -55,16 +55,20 @@ export declare class SqliteTrackStore implements TrackStore {
55
55
  track: LatLngTuple[];
56
56
  }[]>;
57
57
  /**
58
- * Tracks whose last position matches the spatial predicate.
58
+ * Tracks matching the spatial predicate, which depends on who is asking.
59
+ *
60
+ * `intersects: false` — the v1 routes — matches a track's *last* position:
61
+ * "which vessels are near me now". `intersects: true` — the v2 Track API —
62
+ * matches a track that passed through the box at any point in the window,
63
+ * including one that has since left.
59
64
  *
60
65
  * Two filters run, and they are not redundant despite both testing bounds:
61
66
  *
62
67
  * - `contextsInBounds` asks the cell index which contexts have *any* position
63
68
  * near the box. It is a cheap prefilter that keeps the query off every row
64
69
  * in the table, and it decides nothing on its own.
65
- * - `matcher` — the same predicate the in-memory store uses — then tests each
66
- * track's *last* position, which is what the endpoint actually means by
67
- * "in this box". That test is authoritative.
70
+ * - `matcher` — the same predicate the in-memory store uses — then applies
71
+ * whichever rule the caller asked for. That test is authoritative.
68
72
  *
69
73
  * Because `matcher` has the final say, dropping the exact-bounds check inside
70
74
  * `contextsInBounds` does not change any result; it only widens the candidate
@@ -88,7 +92,7 @@ export declare class SqliteTrackStore implements TrackStore {
88
92
  * straight line across the anchorage.
89
93
  */
90
94
  segments(context: Context, window?: TimeWindow): LatLngTuple[][];
91
- prune(maxAge: number): void;
95
+ prune(maxAge: number, keep?: Context): void;
92
96
  close(): void;
93
97
  /** Unsigned round-trip check, used by the tests. */
94
98
  cellIdsFor(context: Context): bigint[];
@@ -1,8 +1,4 @@
1
1
  import { Context } from './types.js';
2
- /**
3
- * Off unless a user opts in, per the request and because the states worth
4
- * pausing on depend on how a boat is used.
5
- */
6
2
  export declare const DEFAULT_PAUSE_STATES: string[];
7
3
  /**
8
4
  * The states a boat is plausibly parked in, offered in the plugin UI.
package/dist/store.d.ts CHANGED
@@ -8,12 +8,12 @@ import { TrackQuery } from './timeWindow.js';
8
8
  * calls in `index.ts` and nothing more, so that adding an implementation cannot
9
9
  * quietly widen what a store is expected to do.
10
10
  *
11
- * Note what is *not* here. `getAllTracks` and `getFilteredTracks` are derived
12
- * operations — filtering is a predicate over whole tracks, which the in-memory
13
- * implementation applies in JS and a database implementation will want to push
14
- * into the query. Both stay on the interface for that reason: a store that can
15
- * filter in SQL must be allowed to, rather than being forced to materialise
16
- * every track so a caller can filter it afterwards.
11
+ * `getAllTracks` and `getFilteredTracks` are derived operations, and they are
12
+ * on the interface deliberately: filtering is a predicate over whole tracks,
13
+ * which the in-memory implementation applies in JS and a database
14
+ * implementation will want to push into the query. A store that can filter in
15
+ * SQL must be allowed to, rather than being forced to materialise every track
16
+ * so a caller can filter it afterwards.
17
17
  */
18
18
  export interface TrackStore {
19
19
  /** Record a position for a context. `timestamp` defaults to now. */
@@ -21,7 +21,8 @@ export interface TrackStore {
21
21
  /**
22
22
  * Seed a context's track, replacing whatever it held.
23
23
  *
24
- * Used by the History API bootstrap at startup. `timestamps` is positional
24
+ * A test helper: it is how a known track is installed without going through
25
+ * the position bus, which throttles on write. `timestamps` is positional
25
26
  * against `track`; points without one are dated to the start of time so a
26
27
  * time-window query treats them as older than anything live.
27
28
  */
@@ -50,8 +51,26 @@ export interface TrackStore {
50
51
  * "vessels currently near here" rather than "vessels that ever passed here".
51
52
  */
52
53
  getFilteredTracks(params: TrackParams, selfPosition?: LatLngTuple, debug?: Debug, query?: TrackQuery): Promise<TrackCollection>;
53
- /** Drop contexts whose newest position is older than `maxAge` ms. */
54
- prune(maxAge: number): void;
54
+ /**
55
+ * Drop contexts whose newest position is older than `maxAge` ms.
56
+ *
57
+ * `keep` is never pruned however long it has been idle. The own vessel's
58
+ * track is the one a user came for — it has to survive a winter on a
59
+ * mooring, a passage with the server off, and any gap in reception. Other
60
+ * vessels age out: a harbour puts hundreds of AIS targets past a receiver in
61
+ * a day, and keeping every one of them forever is not what anybody asked for.
62
+ *
63
+ * An implementation must also apply whatever row-level retention it was
64
+ * configured with, scoped to `keep`. The two are separate: one drops a whole
65
+ * vessel that has gone quiet, the other trims old points from the vessel a
66
+ * user is actually recording, and neither may switch the other off.
67
+ *
68
+ * `Infinity` ages nothing out, which is how a caller asks for the retention
69
+ * pass alone. A negative `maxAge` puts the cutoff in the future and so drops
70
+ * every context — the tests use `-1` for exactly that; the plugin's schema
71
+ * keeps it out of a real configuration.
72
+ */
73
+ prune(maxAge: number, keep?: Context): void;
55
74
  /**
56
75
  * Release any resources held by the store.
57
76
  *
@@ -1,5 +1,6 @@
1
1
  import { TrackStore } from './store.js';
2
2
  import { TrackApi } from './trackApi.js';
3
+ import { TimedPosition, TimeWindow } from './types.js';
3
4
  /**
4
5
  * Serves the v2 Track API from this plugin's store.
5
6
  *
@@ -17,5 +18,19 @@ export interface TrackProviderDeps {
17
18
  selfContext: () => string;
18
19
  /** Gap in ms that starts a new segment; 0 leaves the track as one line. */
19
20
  segmentGap: () => number;
21
+ /**
22
+ * Positions a history provider holds for a context and window, reconciled
23
+ * with the store's own.
24
+ *
25
+ * Injected rather than reached for directly: the reconciliation belongs to
26
+ * the plugin, and the v1 routes have done it since #73 — a query answered
27
+ * through the v2 provider has to give the same answer as the same query
28
+ * answered through v1, or the plugin contradicts itself depending on which
29
+ * route a client happens to use.
30
+ *
31
+ * Resolves to the store's own points unchanged when no provider is
32
+ * installed, which is the common case.
33
+ */
34
+ reconcileWithHistory: (context: string, stored: TimedPosition[], window: TimeWindow | undefined, resolutionMs: number | undefined) => Promise<TimedPosition[]>;
20
35
  }
21
36
  export declare function createTrackProvider(deps: TrackProviderDeps): TrackApi;
package/dist/tracks.d.ts CHANGED
@@ -15,9 +15,12 @@ export interface TracksConfig {
15
15
  fetchInitialTrack?: boolean;
16
16
  }
17
17
  /**
18
- * The in-memory store: a bounded sliding window of positions per context,
19
- * held in RxJS accumulators and lost on restart. `bootstrapSelfTrack()` in
20
- * index.ts is what re-hydrates it from a History provider at startup.
18
+ * The in-memory store: a bounded sliding window of positions per context, held
19
+ * in RxJS accumulators and lost on restart.
20
+ *
21
+ * No longer used by the plugin, which always records to SQLite. It remains as
22
+ * the client-side `TrackAccumulator` this package exports, where a browser
23
+ * holding one vessel's recent track in memory is exactly the right shape.
21
24
  */
22
25
  export declare class Tracks implements TrackStore {
23
26
  tracks: TracksMap;
@@ -44,7 +47,7 @@ export declare class Tracks implements TrackStore {
44
47
  * which tracks match — the filter runs once, here.
45
48
  */
46
49
  getFilteredTimedTracks(params: TrackParams, selfPosition?: LatLngTuple, debug?: Debug, query?: TrackQuery): Promise<TimedTrackCollection>;
47
- prune(maxAge: number): void;
50
+ prune(maxAge: number, keep?: Context): void;
48
51
  }
49
52
  interface AccumulatorParams {
50
53
  resolution: number;
package/dist/utils.d.ts CHANGED
@@ -29,3 +29,12 @@ export declare function validateParameters(params: QueryParameters, defaultMaxRa
29
29
  export declare function createDistanceTo([lat1d, lon1d]: LatLngTuple, debug?: Debug): (d: LatLngTuple | null) => number;
30
30
  export declare function resolveContext(vesselId: string, selfContext?: string): string;
31
31
  export declare function createMatcher(params: TrackParams, selfPosition?: LatLngTuple, debug?: Debug): (track: LatLngTuple[]) => boolean;
32
+ /**
33
+ * A history row is `[timestamp, position]`. Both encodings are legal: the
34
+ * History API types a value as `unknown` and its own comment names "objects
35
+ * (like navigation.position)" alongside primitives, so signalk-questdb returns
36
+ * `{latitude, longitude}` and signalk-to-influxdb2 returns `[lon, lat]`.
37
+ * Accept both — neither is an error, so rejecting one silently yields an empty
38
+ * track rather than a failure anybody can see.
39
+ */
40
+ export declare const historyRowPosition: (row: unknown) => LatLngTuple | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@signalk/tracks-plugin",
3
- "version": "3.0.0-beta.1",
3
+ "version": "3.0.0-beta.2",
4
4
  "description": "Plugin to keep track of tracks of vessels",
5
5
  "type": "module",
6
6
  "exports": {
@@ -1 +0,0 @@
1
- export {};