signalk-questdb 2.2.3 → 2.3.1

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 (47) hide show
  1. package/.coderabbit.yaml +19 -15
  2. package/.release-please-manifest.json +1 -1
  3. package/CLAUDE.md +6 -3
  4. package/README.md +102 -7
  5. package/dist/api-contract.d.ts +46 -0
  6. package/dist/archive.d.ts +44 -0
  7. package/dist/archive.js +117 -0
  8. package/dist/archive.js.map +1 -0
  9. package/dist/config/schema.d.ts +3 -0
  10. package/dist/config/schema.js +27 -0
  11. package/dist/config/schema.js.map +1 -1
  12. package/dist/gpx-import-session.d.ts +69 -0
  13. package/dist/gpx-import-session.js +218 -0
  14. package/dist/gpx-import-session.js.map +1 -0
  15. package/dist/gpx-import.d.ts +135 -0
  16. package/dist/gpx-import.js +452 -0
  17. package/dist/gpx-import.js.map +1 -0
  18. package/dist/history-v1.d.ts +2 -1
  19. package/dist/history-v1.js +77 -19
  20. package/dist/history-v1.js.map +1 -1
  21. package/dist/history-v2.d.ts +2 -1
  22. package/dist/history-v2.js +131 -65
  23. package/dist/history-v2.js.map +1 -1
  24. package/dist/ilp-writer.d.ts +1 -1
  25. package/dist/ilp-writer.js +4 -2
  26. package/dist/ilp-writer.js.map +1 -1
  27. package/dist/index.d.ts +3 -0
  28. package/dist/index.js +239 -10
  29. package/dist/index.js.map +1 -1
  30. package/dist/query-client.js +27 -0
  31. package/dist/query-client.js.map +1 -1
  32. package/dist/retention.d.ts +28 -1
  33. package/dist/retention.js +152 -12
  34. package/dist/retention.js.map +1 -1
  35. package/package.json +2 -1
  36. package/public/assets/PluginConfigurationPanel-DR-y5e3d.js +11 -0
  37. package/public/assets/{_virtual_mf-localSharedImportMap___mfe_internal__signalk_questdb__mf_owner__1-D-Le-Wxu.js → _virtual_mf-localSharedImportMap___mfe_internal__signalk_questdb__mf_owner__1-BUtmcsA-.js} +1 -1
  38. package/public/assets/{dist-BI-Ygd_Z.js → dist-C5M3fhBw.js} +1 -1
  39. package/public/assets/helpers-oIxDNnZJ.js +1 -0
  40. package/public/assets/{hostInit-CdqUOHAJ.js → hostInit-B3lIJd9C.js} +1 -1
  41. package/public/assets/{index-mvK71Kw7.js → index-eAjx9ZuX.js} +2 -2
  42. package/public/assets/{pendingShares-BI6kviiM.js → pendingShares-PvyVL1Z8.js} +2 -2
  43. package/public/assets/utils-v9GZlbdk.js +4 -0
  44. package/public/remoteEntry.js +2 -2
  45. package/public/assets/PluginConfigurationPanel-JR1rvS7f.js +0 -9
  46. package/public/assets/helpers-CsBAxMau.js +0 -1
  47. package/public/assets/utils-BRGuweMc.js +0 -4
package/.coderabbit.yaml CHANGED
@@ -51,10 +51,12 @@ reviews:
51
51
 
52
52
  1. **Plugin runtime** (`src/`, TypeScript, compiled to `dist/`).
53
53
  Loaded by signalk-server as `main` (`dist/index.js`).
54
- 2. **React config panel** (`src/configpanel/`, plain JS — NOT
55
- TypeScript). Bundled by webpack via Module Federation into
54
+ 2. **React config panel** (`src/configpanel/`, TypeScript + JSX).
55
+ Bundled by Vite via Module Federation into
56
56
  `public/remoteEntry.js`. React 19 is a shared singleton with
57
- the host Admin UI.
57
+ the host Admin UI. Imports leaving `src/configpanel/` must be
58
+ `import type`: a value import pulls server-side runtime code
59
+ (typebox, `fs/promises`, saxes) into the browser bundle.
58
60
 
59
61
  Both build outputs ship in the npm package; `prepublishOnly`
60
62
  rebuilds them.
@@ -95,17 +97,17 @@ reviews:
95
97
 
96
98
  ## Config schema is the source of truth
97
99
 
98
- `src/config/schema.ts` uses TypeBox (`@sinclair/typebox`). The
100
+ `src/config/schema.ts` uses TypeBox (the unscoped `typebox` 1.x). The
99
101
  schema is BOTH the JSON Schema rendered by the Signal K Admin UI
100
102
  AND the source of the `Config` TypeScript type via `Static<...>`.
101
103
  Add new options there only.
102
104
 
103
- - The plain-JS configpanel cannot `import` from `schema.ts`
104
- (webpack only resolves `.js`/`.jsx`, no TS loader). The panel
105
- owns its own UI state defaults that must be kept in sync with
106
- the schema defaults. Flag any divergence between the schema's
107
- `default:` value and the corresponding `useState(cfg.X ??
108
- <literal>)` in `PluginConfigurationPanel.js`.
105
+ - The configpanel imports `Config` from `schema.ts` as a type
106
+ only, so it cannot read the schema's defaults. The panel owns
107
+ its own UI state defaults that must be kept in sync with the
108
+ schema defaults. Flag any divergence between the schema's
109
+ `default:` value and the fallback the corresponding `useState`
110
+ in `PluginConfigurationPanel.tsx` gives a missing `cfg.X`.
109
111
  - For runtime fallbacks (e.g. `config.foo ?? 1000` in
110
112
  `src/index.ts`), use the same literal as the schema default for
111
113
  the same reason: schema is canonical, fallback is a backstop
@@ -132,8 +134,8 @@ reviews:
132
134
  - `tsconfig.json` runs strict mode. Do not loosen `strict`,
133
135
  `noImplicitAny`, `noUnusedLocals`, `noUnusedParameters`. Code
134
136
  must narrow `unknown` and avoid `any`.
135
- - The configpanel is plain JS (Babel + `@babel/preset-react`).
136
- Don't suggest migrating it to TSX inside an unrelated PR.
137
+ - The configpanel is TypeScript too, with its own `tsconfig.json`
138
+ (browser libs, `noEmit`), checked by `npm run typecheck:panel`.
137
139
  - Node `>= 22` per `engines` (uses Node's built-in test runner).
138
140
 
139
141
  ## Tests
@@ -205,9 +207,11 @@ reviews:
205
207
  — loose coupling is required.
206
208
  - `signalk.requires: ['signalk-container']` without a matching
207
209
  npm dependency — see "Container integration" above.
208
- - The configpanel being plain JS instead of TS — webpack/babel
209
- setup is intentional, migration is out of scope for feature
210
- PRs.
210
+ - Classic JSX in the configpanel (`jsxRuntime: "classic"` in
211
+ `vite.config.ts`, `"jsx": "react"` in its tsconfig). The
212
+ automatic runtime imports `react/jsx-runtime`, which is not in
213
+ the federation share scope, and a second React instance fails
214
+ to mount the panel.
211
215
  - The single-worker QuestDB env vars
212
216
  (`QDB_SHARED_WORKER_COUNT=1`, etc.) in `src/index.ts` — tuned
213
217
  for Pi/Cerbo, do not "fix" by raising.
@@ -1,3 +1,3 @@
1
1
  {
2
- ".": "2.2.3"
2
+ ".": "2.3.1"
3
3
  }
package/CLAUDE.md CHANGED
@@ -31,11 +31,12 @@ Both build outputs are shipped in the npm package; `prepublishOnly` rebuilds the
31
31
  ### Data flow
32
32
 
33
33
  - Subscribes to the Signal K **streambundle** for all paths and writes filtered/throttled deltas to QuestDB via **ILP over raw TCP** (`src/ilp-writer.ts`) — no QuestDB client library.
34
- - Three tables (`signalk`, `signalk_str`, `signalk_position`) defined and queried in `src/query-client.ts`. All use WAL mode + daily partitioning + dedup.
34
+ - Three tables (`signalk`, `signalk_str`, `signalk_position`) defined and queried in `src/query-client.ts`. All use WAL mode + daily partitioning + dedup. The archives `signalk_archive` and `signalk_position_archive` are defined there too, with their base tables' columns and dedup keys and monthly partitions.
35
35
  - Reads come from two registered providers:
36
36
  - **v2** (`src/history-v2.ts`) — `app.registerHistoryApiProvider()`, REST under `/signalk/v2/api/history/`. Aggregates map directly to QuestDB SQL except `sma`/`ema` (computed client-side).
37
37
  - **v1** (`src/history-v1.ts`) — `app.registerHistoryProvider()`, WebSocket playback with chunked reads.
38
- - Retention (`src/retention.ts`) drops old daily partitions on a timer.
38
+ - Retention (`src/retention.ts`) drops old daily partitions on a timer. Before a partition of `signalk_position` or `signalk` goes, the own vessel's rows the config keeps are copied, thinned, into `signalk_position_archive` / `signalk_archive` (`src/archive.ts`), and the drop waits for that copy to be applied. Reads add an archive table only for a range it covers (`ArchiveSpans`), so a recent window stays on the base table alone; v1 snapshots read it as one more per-table `LATEST ON` branch, never by wrapping the union.
39
+ - GPX import (`src/gpx-import.ts`, `src/gpx-import-session.ts`) streams a sliced upload through saxes into `signalk_position_archive` under source `import.gpx`, thinned by stamping points on interval starts and letting dedup keep one per interval. The caller names the tracks to import: an export mixes the own track with AIS targets and other boats' passages, and nothing in GPX tells them apart.
39
40
  - Startup restore (`src/restore.ts`) optionally replays each vessel's last recorded position back into the live model after a server restart.
40
41
  - Console proxy (`src/console-proxy.ts`) reverse-proxies QuestDB's own web console so the embeddable webapp (`public/index.html`) can host it in the admin UI.
41
42
 
@@ -63,6 +64,8 @@ Their wire shapes live in `src/api-contract.ts` and are shared by both surfaces:
63
64
 
64
65
  `src/test/` contains `node:test` suites for ILP encoding, time range parsing, query building, and v2 history. They run against the **compiled** JS in `dist/test/`, so always build before testing.
65
66
 
67
+ `src/test/history-live.test.ts` runs the v2 provider against a real QuestDB. It drops the plugin's tables, so run it only against a throwaway instance; its header says how.
68
+
66
69
  ## Conventions
67
70
 
68
71
  - TypeScript strict mode everywhere, panel included; do not loosen either `tsconfig.json`. In particular never add `jsx` or a DOM `lib` to the root one — server code referencing `document` would then typecheck clean.
@@ -72,5 +75,5 @@ Their wire shapes live in `src/api-contract.ts` and are shared by both surfaces:
72
75
  - Prettier + eslint flat config (`eslint.config.ts`); run `npm run format` before committing.
73
76
  - The config panel uses React 19 with Module Federation — keep the federation `shared` block in sync with `package.json`'s React version.
74
77
  - **Classic JSX is load-bearing.** `jsxRuntime: "classic"` (vite.config.ts) and `"jsx": "react"` (panel tsconfig) must agree. The automatic runtime imports `react/jsx-runtime`, which is not in the federation `shared` scope, so the remote would carry its own copy of React's jsx runtime — a second React instance whose dispatcher is not the host's, so `useState` reads null and the panel fails to mount. At runtime, with no build error.
75
- - **Imports leaving `src/configpanel/` must be `import type`.** A value import pulls the module's runtime dependencies into the browser bundle (typebox via `config/schema`, `fs/promises` via `host-limits`). After changing panel imports, confirm with `grep -rl "typebox\|fs/promises" public/` — it must print nothing.
78
+ - **Imports leaving `src/configpanel/` must be `import type`.** A value import pulls the module's runtime dependencies into the browser bundle (typebox via `config/schema`, `fs/promises` via `host-limits`, saxes via `gpx-import`). After changing panel imports, confirm with `grep -rl "typebox\|fs/promises\|saxes" public/` — it must print nothing.
76
79
  - `signalk.appIcon` and `signalk.displayName` in `package.json` control how the plugin appears in the Admin UI.
package/README.md CHANGED
@@ -20,6 +20,12 @@ InfluxDB history can be imported.
20
20
  - **Recording controls** — per-path sampling rates, include/exclude filters with
21
21
  globs, own vessel and AIS targets separately, retention by days, LZ4/ZSTD
22
22
  on-disk compression
23
+ - **Archive past retention** — retention drops old days, but the own track, and
24
+ any own-vessel paths you name such as depth, are kept, thinned, in an archive
25
+ the History API reads seamlessly
26
+ - **GPX track import** — a chartplotter's track export of any size goes into
27
+ the archive; the file is read in the browser first, so you pick the own
28
+ vessel's tracks before anything is uploaded
23
29
  - **Startup restore** — AIS targets back on the chart right after a server
24
30
  restart, in their ship-type colour
25
31
  - **WAL watchdog** — a table QuestDB has silently stopped applying is detected,
@@ -116,6 +122,9 @@ rootless Podman.
116
122
  | Allow `sourcePolicy=all` | `false` | Let History API callers split a path into one column per recording source (see History API) |
117
123
  | QuestDB console webapp | `true` | Serve QuestDB's console inside the Signal K admin UI, admin only (see Console webapp) |
118
124
  | Retention (days) | `0` | Drop daily partitions older than this (0 = keep forever) |
125
+ | Keep own track | `true` | Archive the own vessel's positions before retention drops them (see Retention and the archive) |
126
+ | Archived own-vessel paths | _(empty)_ | Glob patterns of own-vessel numeric paths to archive the same way, e.g. `environment.depth.*` |
127
+ | Archive resolution (s) | `10` | Archived rows are thinned to the first sample per interval (0 = every row) |
119
128
  | Filter mode | `exclude` | `exclude` the matching paths, or `include` only the matching paths |
120
129
  | Path patterns | _(empty)_ | Glob patterns, one per line, e.g. `environment.wind.*`; empty records everything |
121
130
  | Compression codec | `lz4` | On-disk WAL segments and Parquet exports: `none`, `lz4` (fast) or `zstd` (smaller) |
@@ -135,6 +144,10 @@ Three tables, all with WAL mode, daily partitioning, and deduplication:
135
144
  | `signalk_str` | String values | `ts`, `path` (SYMBOL), `context` (SYMBOL), `source` (SYMBOL), `value_str` (VARCHAR), `value_kind` (SYMBOL) |
136
145
  | `signalk_position` | Positions | `ts`, `context` (SYMBOL), `source` (SYMBOL), `lat` (DOUBLE), `lon` (DOUBLE) |
137
146
 
147
+ What retention keeps of the own vessel lives in two more tables,
148
+ `signalk_archive` and `signalk_position_archive`, with the same columns and
149
+ monthly partitions (see Retention and the archive).
150
+
138
151
  The own vessel is stored as context `self`, whatever its identity; other vessels
139
152
  keep their `vessels.urn:…` context. The history API translates `self` back to
140
153
  `vessels.self`.
@@ -221,9 +234,33 @@ if QuestDB is unreachable for longer than that covers, the oldest lines are
221
234
  dropped and counted. A clean Signal K shutdown flushes first; a hard crash loses
222
235
  at most one interval.
223
236
 
224
- **Retention** drops whole daily partitions once they are older than the
225
- configured days; 0 keeps everything. **Compression** applies LZ4 or ZSTD to the
226
- WAL segments on disk (and to Parquet exports).
237
+ **Compression** applies LZ4 or ZSTD to the WAL segments on disk (and to
238
+ Parquet exports).
239
+
240
+ ### Retention and the archive
241
+
242
+ Retention drops whole daily partitions once they lie entirely before the
243
+ configured number of days; 0 keeps everything. QuestDB cannot delete single
244
+ rows, so keeping part of a day means copying it out first. Before a day of
245
+ `signalk_position` or `signalk` is dropped, the own vessel's rows that the
246
+ settings ask to keep are copied into an archive table, thinned to one row per
247
+ **Archive resolution** interval, and the day is dropped only once QuestDB has
248
+ applied the copy. A copy that fails leaves the day in place for the next run.
249
+
250
+ | Table | Holds |
251
+ | -------------------------- | --------------------------------------------------------------- |
252
+ | `signalk_position_archive` | Own-vessel positions (**Keep own track**, on by default) |
253
+ | `signalk_archive` | Own-vessel numeric paths matching **Archived own-vessel paths** |
254
+
255
+ Both have the columns and dedup keys of their base table and are partitioned by
256
+ month. Retention never drops from them, other vessels are never archived, and
257
+ strings are not archived. The History API reads the archive together with the
258
+ base tables for any range it covers, so a track or a depth series continues
259
+ past the retention boundary without a seam. An archived row is the first sample
260
+ of its interval and is stamped on the interval's start.
261
+
262
+ Size, measured: a position row takes 32 bytes, so a vessel recorded around the
263
+ clock fills about 100 MB a year at a 10 s resolution, 34 MB at 30 s.
227
264
 
228
265
  ## History API
229
266
 
@@ -273,8 +310,14 @@ arithmetic.
273
310
 
274
311
  `navigation.position` answers `first`, `last` and `middle_index` — each a point
275
312
  the vessel was at. Any other method runs `first`, and the column's `method` says
276
- so. A text path (rows in `signalk_str`) answers `first` and `last`;
277
- `middle_index` and the moving averages read the numeric table only.
313
+ so. A text path (rows in `signalk_str`) answers `first`, `last` and
314
+ `middle_index`. With a resolution, `average`, `min`, `max` and `mid` run `last`,
315
+ the state in force at the bucket's end, and the column's `method` says so.
316
+ `sma` and `ema` read the numeric table only, so a text path has no values for
317
+ them.
318
+
319
+ A method name outside the table above fails the request with an error naming
320
+ the path and the method.
278
321
 
279
322
  Query example:
280
323
 
@@ -545,6 +588,49 @@ recorded during the rebuild are carried across. Should recording be fast enough
545
588
  to fill the writer's buffer during those seconds, the samples it could not keep
546
589
  are counted and the panel says how many. Run the import again afterwards.
547
590
 
591
+ ## Importing a GPX track
592
+
593
+ A chartplotter's exported track goes into the position archive (see Retention
594
+ and the archive), where retention never drops it, under the source
595
+ `import.gpx`. An export can be gigabytes, a four-year TimeZero export is
596
+ 1.5 GB, so the file is sent in slices and parsed as it arrives. Memory stays at
597
+ one slice, whatever the file's size.
598
+
599
+ In the configuration panel, **GPX track import** reads the chosen file in the
600
+ browser first, which takes seconds even for a gigabyte, and lists its tracks by
601
+ name with their points and period. It proposes the own vessel's tracks: the
602
+ largest group whose name is not an AIS target's, and any other name that
603
+ recurs. It flags a group that lay more than a kilometre from a larger one at
604
+ the same time, since that was another vessel. Nothing is uploaded until
605
+ **Import** is pressed; the import then shows its progress and can be aborted.
606
+
607
+ - **Which tracks.** An export mixes the own track with AIS targets and with
608
+ passages imported into the plotter from elsewhere, and GPX does not say whose
609
+ a track is. The import takes the names of the tracks to import (`tracks`);
610
+ every other track is skipped and listed in the summary. In a TimeZero export
611
+ the own track is named `Own Ship`, or the plotter's translation of it, such
612
+ as `Eigenes Schiff`.
613
+ - **Thinning.** Points are thinned to the **Archive resolution**: each is
614
+ stamped on the start of its interval, and the archive keeps one row per
615
+ interval. 11.5 million points over four years become 3.2 million rows,
616
+ about 100 MB, at 10 s.
617
+ - **Overlap.** With `fillGapsOnly` (the default), a point is left out where the
618
+ vessel already has recorded positions in the same five minutes. Two
619
+ receivers' fixes interleaved in one series draw a sawtooth.
620
+ - **Doing it again.** Importing the same file again changes nothing, and a
621
+ stopped import can simply be restarted: the archive's dedup keys absorb the
622
+ rows already written.
623
+ - **Positions only.** The import takes a position and a time from each point;
624
+ depth, temperature or speed in a point's extensions are not imported. Depth
625
+ along the track beyond retention comes from live recording: list
626
+ `environment.depth.*` under **Archived own-vessel paths**, and retention keeps
627
+ it from then on.
628
+ - **Strict reading.** Coordinates must be decimals within range and times
629
+ `xsd:dateTime`; anything else is counted as invalid. A longitude written just
630
+ past ±180, as TimeZero does at the antimeridian, is wrapped. A file that is
631
+ not well-formed XML stops the import at the error, keeping the rows before
632
+ it.
633
+
548
634
  ## Export and backup
549
635
 
550
636
  **Data Export** in the panel, and
@@ -556,7 +642,10 @@ CSV.
556
642
  `GET /plugins/signalk-questdb/api/full-export/:table?from=…&to=…` streams one
557
643
  whole table as Parquet, for snapshot and backup tooling that wants the full
558
644
  content sliced into dedup-friendly shards (kopia, for instance). Tables:
559
- `signalk`, `signalk_str`, `signalk_position` (`/full-export/tables` lists them).
645
+ `signalk`, `signalk_str`, `signalk_position`, `signalk_archive`, `signalk_position_archive`
646
+ (`/full-export/tables` lists them). The archive tables hold the own vessel's
647
+ history beyond retention and every imported track, so a backup that is meant to
648
+ outlive retention needs them too.
560
649
 
561
650
  - `from` and `to` are optional but must be set together: omit both for the whole
562
651
  table, or pass both as ISO 8601 timestamps for a half-open `[from, to)` window
@@ -598,6 +687,11 @@ default.
598
687
  | GET | `/export?from=...&to=...&format=parquet` | Parquet or CSV export of the `signalk` numeric table (date range required) |
599
688
  | GET | `/full-export/tables` | List the tables the full-export route serves |
600
689
  | GET | `/full-export/:table?from=...&to=...` | Stream a table as Parquet; optional half-open `[from, to)` window for slicing into shards |
690
+ | GET | `/gpx-import` | The current or last GPX import |
691
+ | POST | `/gpx-import` | Announce an import: `{filename, size, tracks, fillGapsOnly}`, see Importing a GPX track |
692
+ | PUT | `/gpx-import/:id?offset=N` | One slice of the file, as `application/octet-stream`, at most 8 MiB |
693
+ | POST | `/gpx-import/:id/finish` | End the import once every byte is in; answers when QuestDB has applied it |
694
+ | DELETE | `/gpx-import/:id` | Abort the import; what it wrote stays |
601
695
 
602
696
  ## Deployment
603
697
 
@@ -687,7 +781,8 @@ and no client-visible error is raised anywhere. Left alone, recording stalls
687
781
  silently for as long as nobody looks.
688
782
 
689
783
  The plugin watches for this. A suspended table of its own (in external mode,
690
- only the three tables above — never someone else's) is logged loudly, raised as
784
+ only the three tables above and the two archive tables — never someone else's)
785
+ is logged loudly, raised as
691
786
  the Signal K notification `notifications.signalk-questdb.walSuspended`, and
692
787
  shown on the status card as **WAL suspended**. Two causes need two remedies:
693
788
 
@@ -268,3 +268,49 @@ export interface MigrationStatusResponse extends ApiError {
268
268
  /** Present, when no run is active, if a stopped import can be continued. */
269
269
  interrupted?: MigrationInterrupted;
270
270
  }
271
+ /** What a GPX import has read and written so far. */
272
+ export interface GpxImportSummary {
273
+ /** Bytes of the document parsed. */
274
+ bytes: number;
275
+ tracks: number;
276
+ /** Points per track name, of the tracks selected for import. */
277
+ importedTracks: Record<string, number>;
278
+ /** Points per track name, of the tracks left out. */
279
+ skippedTracks: Record<string, number>;
280
+ /** Track points read, in every track. */
281
+ points: number;
282
+ /** Rows written to the archive. */
283
+ imported: number;
284
+ /** Points that fell in an interval already written from the same segment. */
285
+ thinned: number;
286
+ /** Points where the vessel already had recorded positions. */
287
+ alreadyRecorded: number;
288
+ /** Points with a position or time GPX cannot express, or none at all. */
289
+ invalid: number;
290
+ /** First and last imported instant, ISO. */
291
+ from: string | null;
292
+ to: string | null;
293
+ }
294
+ /**
295
+ * receiving: slices are accepted. finishing: the last slice is in and the
296
+ * import waits for QuestDB to apply it. The other three are final.
297
+ */
298
+ export type GpxImportState = "receiving" | "finishing" | "finished" | "failed" | "aborted";
299
+ /** One GPX import, as /api/gpx-import reports it. */
300
+ export interface GpxImportStatus {
301
+ id: string;
302
+ filename: string;
303
+ /** Size of the file the client announced, in bytes. */
304
+ size: number;
305
+ /** Bytes accepted so far: where the next slice must start. */
306
+ received: number;
307
+ state: GpxImportState;
308
+ summary: GpxImportSummary;
309
+ error?: string;
310
+ startedAt: string;
311
+ updatedAt: string;
312
+ }
313
+ /** Every /api/gpx-import endpoint answers with the import it acted on. */
314
+ export interface GpxImportResponse extends ApiError {
315
+ import?: GpxImportStatus | null;
316
+ }
@@ -0,0 +1,44 @@
1
+ import type { QueryClient } from "./query-client.js";
2
+ export declare const ARCHIVE_TABLES: {
3
+ readonly signalk: "signalk_archive";
4
+ readonly signalk_position: "signalk_position_archive";
5
+ };
6
+ export type ArchivedTable = keyof typeof ARCHIVE_TABLES;
7
+ /** Whether the archive holds rows of a table that a read must include. */
8
+ export interface ArchiveReader {
9
+ covers(table: ArchivedTable, from: string, to: string): boolean;
10
+ }
11
+ export declare const NO_ARCHIVE: ArchiveReader;
12
+ /**
13
+ * What a read selects from: the base table alone, or the base table and its
14
+ * archive merged in time order. The base table alone keeps the common case —
15
+ * a recent window, which the archive never reaches — on exactly the query it
16
+ * has always been.
17
+ *
18
+ * The merge has to declare `ts` its designated timestamp again, or SAMPLE BY
19
+ * and LATEST ON reject it, and that declaration is only true because of the
20
+ * ORDER BY. A time filter on the outer query still reaches both tables as an
21
+ * interval scan (verified with EXPLAIN on QuestDB 9.4 and 10.0).
22
+ */
23
+ export declare function readFrom(table: ArchivedTable, withArchive: boolean): string;
24
+ /**
25
+ * The time span each archive table holds, cached so that deciding whether a
26
+ * read needs it costs no query. Refreshed after anything writes to the
27
+ * archive; until the first refresh nothing is archived as far as reads know.
28
+ */
29
+ export declare class ArchiveSpans implements ArchiveReader {
30
+ private readonly queryClient;
31
+ private readonly spans;
32
+ private read;
33
+ constructor(queryClient: Pick<QueryClient, "exec">);
34
+ /** Whether a refresh has read every archive table at least once. */
35
+ get known(): boolean;
36
+ refresh(): Promise<void>;
37
+ covers(table: ArchivedTable, from: string, to: string): boolean;
38
+ }
39
+ /**
40
+ * Wait until QuestDB has applied everything committed to `table`'s WAL when
41
+ * this was called. A row is readable only once applied, and a suspended
42
+ * table never gets there, so that fails at once rather than at the deadline.
43
+ */
44
+ export declare function waitForApply(queryClient: Pick<QueryClient, "exec">, table: string, timeoutMs: number, pollMs?: number): Promise<void>;
@@ -0,0 +1,117 @@
1
+ // The archive: what retention keeps of the own vessel once it drops the rest.
2
+ //
3
+ // Retention drops whole daily partitions, because QuestDB has no row-level
4
+ // DELETE, so "drop everything but the own track" cannot be a filter on the
5
+ // drop. Before a partition goes, the rows worth keeping are copied, thinned,
6
+ // into a table of their own that retention never touches. Reads add that
7
+ // table back in wherever it covers the asked range.
8
+ //
9
+ // The archive tables mirror their base tables' columns and dedup keys, so a
10
+ // copy that is repeated — a run interrupted between the copy and the drop —
11
+ // upserts onto itself instead of doubling rows. They are partitioned by month:
12
+ // an archive is sparse and lives for years, and a partition per day would
13
+ // leave thousands of near-empty ones behind.
14
+ import { isMissingTable } from "./schema-errors.js";
15
+ export const ARCHIVE_TABLES = {
16
+ signalk: "signalk_archive",
17
+ signalk_position: "signalk_position_archive",
18
+ };
19
+ const COLUMNS = {
20
+ signalk: "ts, path, context, source, value",
21
+ signalk_position: "ts, context, source, lat, lon",
22
+ };
23
+ export const NO_ARCHIVE = { covers: () => false };
24
+ /**
25
+ * What a read selects from: the base table alone, or the base table and its
26
+ * archive merged in time order. The base table alone keeps the common case —
27
+ * a recent window, which the archive never reaches — on exactly the query it
28
+ * has always been.
29
+ *
30
+ * The merge has to declare `ts` its designated timestamp again, or SAMPLE BY
31
+ * and LATEST ON reject it, and that declaration is only true because of the
32
+ * ORDER BY. A time filter on the outer query still reaches both tables as an
33
+ * interval scan (verified with EXPLAIN on QuestDB 9.4 and 10.0).
34
+ */
35
+ export function readFrom(table, withArchive) {
36
+ if (!withArchive)
37
+ return table;
38
+ const columns = COLUMNS[table];
39
+ return (`((SELECT ${columns} FROM ${table} UNION ALL ` +
40
+ `SELECT ${columns} FROM ${ARCHIVE_TABLES[table]}) ORDER BY ts) TIMESTAMP(ts)`);
41
+ }
42
+ /**
43
+ * The time span each archive table holds, cached so that deciding whether a
44
+ * read needs it costs no query. Refreshed after anything writes to the
45
+ * archive; until the first refresh nothing is archived as far as reads know.
46
+ */
47
+ export class ArchiveSpans {
48
+ queryClient;
49
+ spans = new Map();
50
+ read = false;
51
+ constructor(queryClient) {
52
+ this.queryClient = queryClient;
53
+ }
54
+ /** Whether a refresh has read every archive table at least once. */
55
+ get known() {
56
+ return this.read;
57
+ }
58
+ async refresh() {
59
+ for (const table of Object.keys(ARCHIVE_TABLES)) {
60
+ try {
61
+ const result = await this.queryClient.exec(`SELECT min(ts), max(ts) FROM ${ARCHIVE_TABLES[table]}`);
62
+ const [from, to] = result.dataset[0] ?? [];
63
+ if (typeof from === "string" && typeof to === "string") {
64
+ this.spans.set(table, [Date.parse(from), Date.parse(to)]);
65
+ }
66
+ else {
67
+ this.spans.delete(table);
68
+ }
69
+ }
70
+ catch (err) {
71
+ // A missing table archives nothing. Any other failure keeps the span
72
+ // already known: forgetting it would hide archived history from
73
+ // every read until the next refresh.
74
+ if (isMissingTable(err))
75
+ this.spans.delete(table);
76
+ else
77
+ throw err;
78
+ }
79
+ }
80
+ this.read = true;
81
+ }
82
+ covers(table, from, to) {
83
+ const span = this.spans.get(table);
84
+ if (!span)
85
+ return false;
86
+ return Date.parse(from) <= span[1] && Date.parse(to) >= span[0];
87
+ }
88
+ }
89
+ /**
90
+ * Wait until QuestDB has applied everything committed to `table`'s WAL when
91
+ * this was called. A row is readable only once applied, and a suspended
92
+ * table never gets there, so that fails at once rather than at the deadline.
93
+ */
94
+ export async function waitForApply(queryClient, table, timeoutMs, pollMs = 1000) {
95
+ const state = async () => {
96
+ const result = await queryClient.exec(`SELECT suspended, writerTxn, sequencerTxn FROM wal_tables() WHERE name = '${table}'`);
97
+ const [suspended, writerTxn, sequencerTxn] = result.dataset[0] ?? [];
98
+ if (typeof writerTxn !== "number" || typeof sequencerTxn !== "number") {
99
+ throw new Error(`${table} is not a WAL table`);
100
+ }
101
+ if (suspended === true) {
102
+ throw new Error(`${table} is suspended, so the rows cannot be applied`);
103
+ }
104
+ return { writerTxn, sequencerTxn };
105
+ };
106
+ const target = (await state()).sequencerTxn;
107
+ const deadline = Date.now() + timeoutMs;
108
+ for (;;) {
109
+ if ((await state()).writerTxn >= target)
110
+ return;
111
+ if (Date.now() > deadline) {
112
+ throw new Error(`${table} did not apply the rows within the timeout`);
113
+ }
114
+ await new Promise((resolve) => setTimeout(resolve, pollMs));
115
+ }
116
+ }
117
+ //# sourceMappingURL=archive.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"archive.js","sourceRoot":"","sources":["../src/archive.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,9 @@ export declare const ConfigSchema: Type.TObject<{
23
23
  historySourcePolicyAll: Type.TBoolean;
24
24
  enableConsole: Type.TBoolean;
25
25
  retentionDays: Type.TNumber;
26
+ archiveOwnTrack: Type.TBoolean;
27
+ archivePaths: Type.TArray<Type.TString>;
28
+ archiveResolutionSeconds: Type.TNumber;
26
29
  networkName: Type.TString;
27
30
  exposeToContainers: Type.TBoolean;
28
31
  compression: Type.TUnion<[Type.TLiteral<"none">, Type.TLiteral<"lz4">, Type.TLiteral<"zstd">]>;
@@ -106,6 +106,22 @@ export const ConfigSchema = Type.Object({
106
106
  default: 0,
107
107
  title: "Retention (days, 0 = keep forever)",
108
108
  }),
109
+ archiveOwnTrack: Type.Boolean({
110
+ default: true,
111
+ title: "Keep own track beyond retention",
112
+ description: "Before retention drops a day, copy the own vessel's positions into an archive that retention never touches, thinned to the archive resolution. History reads include the archive, so the own track stays available however old it is.",
113
+ }),
114
+ archivePaths: Type.Array(Type.String(), {
115
+ default: [],
116
+ title: "Archived own-vessel paths",
117
+ description: "Numeric paths of the own vessel to archive the same way, as glob patterns, e.g. environment.depth.*. Other vessels are never archived.",
118
+ }),
119
+ archiveResolutionSeconds: Type.Number({
120
+ default: 10,
121
+ minimum: 0,
122
+ title: "Archive resolution (seconds)",
123
+ description: "Archived rows are thinned to the first sample in each interval of this length. 0 archives every recorded row.",
124
+ }),
109
125
  networkName: Type.String({
110
126
  default: "sk-network",
111
127
  title: "Container network",
@@ -168,6 +184,17 @@ export function normalizeConfig(config) {
168
184
  // than restoreOnStart: the console only ever does what an authenticated
169
185
  // admin types, so a missing key should not hide the tool.
170
186
  enableConsole: config.enableConsole !== false,
187
+ // On unless explicitly disabled: turning retention on must not silently
188
+ // drop the own track along with everything else.
189
+ archiveOwnTrack: config.archiveOwnTrack !== false,
190
+ archivePaths: Array.isArray(config.archivePaths)
191
+ ? config.archivePaths.filter((p) => typeof p === "string" && p.trim() !== "")
192
+ : [],
193
+ archiveResolutionSeconds: typeof config.archiveResolutionSeconds === "number" &&
194
+ Number.isFinite(config.archiveResolutionSeconds) &&
195
+ config.archiveResolutionSeconds >= 0
196
+ ? Math.floor(config.archiveResolutionSeconds)
197
+ : 10,
171
198
  // Missing resource limits get the schema defaults. Config-panel saves
172
199
  // used to drop these keys entirely (issue #98), which ran QuestDB with
173
200
  // NO memory cap instead of the promised 768m — this backfill repairs
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"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"}