signalk-questdb 2.1.4 → 2.1.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.
Files changed (45) hide show
  1. package/README.md +112 -27
  2. package/dist/api-contract.d.ts +45 -0
  3. package/dist/configpanel/responses.d.ts +28 -1
  4. package/dist/configpanel/responses.js +74 -0
  5. package/dist/configpanel/responses.js.map +1 -1
  6. package/dist/ilp-writer.d.ts +52 -0
  7. package/dist/ilp-writer.js +88 -7
  8. package/dist/ilp-writer.js.map +1 -1
  9. package/dist/index.js +477 -45
  10. package/dist/index.js.map +1 -1
  11. package/dist/migration-checkpoint.d.ts +170 -0
  12. package/dist/migration-checkpoint.js +276 -0
  13. package/dist/migration-checkpoint.js.map +1 -0
  14. package/dist/migration-cleanup.d.ts +48 -0
  15. package/dist/migration-cleanup.js +155 -0
  16. package/dist/migration-cleanup.js.map +1 -0
  17. package/dist/migration.d.ts +134 -17
  18. package/dist/migration.js +694 -158
  19. package/dist/migration.js.map +1 -1
  20. package/dist/questdb-mount.d.ts +106 -0
  21. package/dist/questdb-mount.js +173 -0
  22. package/dist/questdb-mount.js.map +1 -0
  23. package/dist/restore.js +44 -8
  24. package/dist/restore.js.map +1 -1
  25. package/package.json +2 -2
  26. package/public/assets/AppPanel-p27aQlBS.js +1 -0
  27. package/public/assets/PluginConfigurationPanel-BO_4OxBo.js +9 -0
  28. package/public/assets/_virtual_mf-localSharedImportMap___mfe_internal__signalk_questdb__mf_owner__1-BiBqFhVp.js +1 -0
  29. package/public/assets/_virtual_mf___mfe_internal__signalk_questdb__mf_owner__275086164343916__loadShare__react__loadShare__.js-BWO9MGdR.js +1 -0
  30. package/public/assets/_virtual_mf___mfe_internal__signalk_questdb__mf_owner__275086164343916__prebuild__react__prebuild__-D2g10OtU.js +1 -0
  31. package/public/assets/dist-BK97MX24.js +1 -0
  32. package/public/assets/helpers-Bizl8Raj.js +1 -0
  33. package/public/assets/hostInit-BFZIDSUI.js +1 -0
  34. package/public/assets/index-bERRzqQZ.js +2 -0
  35. package/public/assets/pendingShares-BS4kO2ec.js +2 -0
  36. package/public/assets/{dist-BgxWXJ5y.js → utils-jR9fY54i.js} +2 -2
  37. package/public/assets/{vite-preload-helper-HclGiUj8.js → vite-preload-helper-uBIymjUX.js} +1 -1
  38. package/public/remoteEntry.js +2 -2
  39. package/public/assets/AppPanel-CNFtJpeq.js +0 -1
  40. package/public/assets/PluginConfigurationPanel-CR4mD9IH.js +0 -9
  41. package/public/assets/_virtual_mf-localSharedImportMap___mfe_internal__signalk_questdb__mf_owner__1-7tb3Q-Bx.js +0 -1
  42. package/public/assets/_virtual_mf___mfe_internal__signalk_questdb__mf_owner__1__loadShare__react__loadShare__.js-DzhF4wXp.js +0 -1
  43. package/public/assets/hostInit-CyS9_M1c.js +0 -1
  44. package/public/assets/index-Dh-t5VMR.js +0 -1
  45. package/public/assets/virtualExposes-BBIkbTtD.js +0 -1
@@ -1,10 +1,11 @@
1
1
  import type { ILPWriter } from "./ilp-writer.js";
2
- import type { MigrationBucket, MigrationMeasurement, MigrationProgress, MigrationRunState } from "./api-contract.js";
2
+ import type { CheckpointStore, ConfirmStored, MigrationCheckpoint } from "./migration-checkpoint.js";
3
+ import type { MigrationBucket, MigrationMeasurement, MigrationProgress, MigrationResumePoint, MigrationRunState } from "./api-contract.js";
3
4
  /**
4
- * How much time one read covers. Small enough that a dense measurement does
5
- * not return a result set too large to hold, large enough that a sparse one
6
- * does not need thousands of round trips. A day is the natural unit for boat
7
- * data: it bounds a busy passage to a few hundred thousand points.
5
+ * How much time one query covers. Large enough that a sparse measurement does
6
+ * not need thousands of round trips, small enough that one query stays cheap
7
+ * for InfluxDB and progress moves in visible steps. It does not bound memory:
8
+ * a window is streamed in batches, however many points it holds.
8
9
  */
9
10
  export declare const DEFAULT_WINDOW_MS: number;
10
11
  export interface InfluxAuth {
@@ -25,8 +26,18 @@ export interface MigrationRequest {
25
26
  /** ISO instants bounding the import. */
26
27
  from: string;
27
28
  to: string;
28
- /** Signal K context to write rows under. */
29
+ /** Signal K context to write the source's own vessel under. */
29
30
  context: string;
31
+ /**
32
+ * The `context` tag value the source uses for its own vessel. Rows tagged
33
+ * with it are written under `context`; rows tagged otherwise are other
34
+ * vessels and keep their tag (see `others`); untagged rows are the own
35
+ * vessel too. Absent: every row is the own vessel.
36
+ */
37
+ sourceSelfContext?: string;
38
+ /** What to do with rows of other vessels: keep them under their own
39
+ * context (the default), or leave them out. */
40
+ others?: "keep" | "skip";
30
41
  /** Restrict to these measurements; empty/absent means all of them. */
31
42
  measurements?: string[];
32
43
  /** `source` tag written on every imported row, so it is distinguishable. */
@@ -47,18 +58,70 @@ export declare function listMeasurements(req: {
47
58
  auth?: InfluxAuth;
48
59
  }, fetchImpl?: typeof fetch): Promise<MigrationMeasurement[]>;
49
60
  /**
50
- * Minimal annotated-CSV reader for Flux responses.
61
+ * The `context` tag values in a bucket/database: the vessels the source holds.
62
+ * Both Signal K InfluxDB writers tag every point with the delta's context, so
63
+ * this is how the source's own vessel is told from the AIS targets it recorded.
64
+ * A source with no such tag — a non-Signal K schema — returns none.
65
+ */
66
+ export declare function listContexts(req: {
67
+ url: string;
68
+ type: string;
69
+ bucket: string;
70
+ auth?: InfluxAuth;
71
+ }, fetchImpl?: typeof fetch): Promise<string[]>;
72
+ /**
73
+ * Whether a string is a Signal K context this plugin will file history under:
74
+ * a vessel, aton, aircraft or SAR target with a well-formed identifier.
75
+ */
76
+ export declare function isSignalKContext(context: string): boolean;
77
+ /**
78
+ * Incremental annotated-CSV reader for Flux responses.
79
+ *
80
+ * Fed one physical line at a time, so a response is never held whole: the
81
+ * caller streams the body through it and sees each record as it completes.
51
82
  *
52
83
  * Flux emits one or more *tables*, each preceded by annotation lines starting
53
84
  * with `#` and then a header row. A response can therefore change its column
54
85
  * layout partway through, which is why the header is tracked per block rather
55
86
  * than read once at the top.
56
87
  */
88
+ export declare class AnnotatedCsvReader {
89
+ private header;
90
+ private datatypes;
91
+ private groups;
92
+ private types;
93
+ private grouped;
94
+ private pending;
95
+ /**
96
+ * Feed one physical line, without its `\n`. Returns the record it
97
+ * completes, or null for annotations, headers, blanks and partial records.
98
+ *
99
+ * A quoted CSV value may contain a newline — verified against a live 2.9.1,
100
+ * which returns `"line1\r\nline2"` for a string value holding one. Splitting
101
+ * on newlines alone would tear that record in half and produce two garbage
102
+ * rows, so physical lines are joined until the quotes balance ("" inside a
103
+ * quoted value is an escaped quote and does not change the balance).
104
+ * \r is stripped per PHYSICAL line before joining: a quoted value spanning
105
+ * two CRLF lines would otherwise keep the \r of the first inside the joined
106
+ * value, so the imported string silently carries a stray carriage return.
107
+ */
108
+ push(physicalLine: string): AnnotatedRecord | null;
109
+ /** Call once after the last line. */
110
+ end(): AnnotatedRecord | null;
111
+ private logicalLine;
112
+ }
113
+ /** Parse a whole annotated-CSV response. For small results only. */
57
114
  export declare function parseAnnotatedCsv(text: string): AnnotatedRecord[];
58
- /** One Flux CSV record plus the `#datatype` of each of its columns. */
115
+ /**
116
+ * One Flux CSV record, plus what the table's annotations say about its columns.
117
+ * `types` and `grouped` belong to the table and are shared by all its records.
118
+ */
59
119
  export interface AnnotatedRecord {
60
120
  values: Record<string, string>;
61
- types: Record<string, string>;
121
+ /** `#datatype` per column. */
122
+ types: Readonly<Record<string, string>>;
123
+ /** Columns in the table's group key (`#group` true): tags, never fields. */
124
+ grouped: ReadonlySet<string>;
62
125
  }
63
126
  /**
64
127
  * Parse an RFC3339 instant into nanoseconds since the epoch, preserving digits
@@ -73,12 +136,27 @@ export declare function rfc3339ToNanos(value: string): bigint | null;
73
136
  /**
74
137
  * The Signal K path a measurement+field pair represents.
75
138
  *
76
- * signalk-to-influxdb writes the path as the measurement name and the value in
77
- * a field called `value`. Anything else (a measurement with several named
78
- * fields) is a non-Signal K schema, so the field name is appended to keep the
79
- * two apart instead of overwriting one with the other.
139
+ * The Signal K InfluxDB plugins write the path as the measurement name and the
140
+ * value in one of PATH_VALUE_FIELDS. Anything else (a measurement with several
141
+ * named fields) is a non-Signal K schema, so the field name is appended to keep
142
+ * the two apart instead of overwriting one with the other.
80
143
  */
81
144
  export declare function toSignalKPath(measurement: string, field: string): string;
145
+ /**
146
+ * Decode `jsonValue` fields back into the values they encode.
147
+ *
148
+ * Left as the string it arrives as, a position would be recorded in
149
+ * `signalk_str` as text and `signalk_position` would stay empty. Decoded, it
150
+ * routes exactly like the live value did: a position to the position table,
151
+ * any other object to its scalar leaves.
152
+ *
153
+ * A value that does not parse cannot be represented, so it is dropped — and
154
+ * reported, so it lands in the run's skipped total.
155
+ */
156
+ export declare function decodeJsonValueRows(rows: SourceRow[]): {
157
+ rows: SourceRow[];
158
+ dropped: number;
159
+ };
82
160
  /** Coerce an InfluxDB field value (always text over CSV) to a JS value. */
83
161
  export declare function coerceValue(raw: string, dataType?: string): unknown;
84
162
  export interface MigrationRunHandle {
@@ -90,6 +168,7 @@ export interface MigrationRunHandle {
90
168
  finishedAt?: string;
91
169
  progress: MigrationProgress;
92
170
  error?: string;
171
+ resumedFrom?: MigrationResumePoint;
93
172
  cancel(): void;
94
173
  }
95
174
  /**
@@ -104,6 +183,7 @@ export declare class MigrationRun implements MigrationRunHandle {
104
183
  startedAt: string;
105
184
  finishedAt?: string;
106
185
  error?: string;
186
+ resumedFrom?: MigrationResumePoint;
107
187
  progress: MigrationProgress;
108
188
  private cancelled;
109
189
  constructor(id: string, url: string, bucket: string);
@@ -120,26 +200,63 @@ export declare class MigrationRun implements MigrationRunHandle {
120
200
  */
121
201
  export declare function runMigration(req: MigrationRequest, writer: Pick<ILPWriter, "writeAtNanos" | "writeStringAtNanos" | "writePositionAtNanos" | "pendingLines"> & {
122
202
  readonly droppedLineCount?: number;
203
+ readonly enqueuedLineCount?: number;
204
+ readonly settledLineCount?: number;
205
+ readonly atCapacity?: boolean;
123
206
  }, run: MigrationRun, deps?: {
124
207
  fetchImpl?: typeof fetch;
125
208
  debug?: (msg: string) => void;
126
209
  /** Injected so tests don't wait on real backpressure sleeps. */
127
210
  sleep?: (ms: number) => Promise<void>;
211
+ /** Injected so tests don't wait out a real stalled read. */
212
+ readIdleTimeoutMs?: number;
213
+ /** Where the import's position is kept, so a later run can resume it. */
214
+ checkpoints?: CheckpointStore;
215
+ /**
216
+ * A checkpoint to continue from. Only honoured if it belongs to this same
217
+ * import — anything else is ignored and the run starts from the beginning.
218
+ */
219
+ resumeFrom?: MigrationCheckpoint;
220
+ /** Injected so tests don't wait out the real checkpoint lag. */
221
+ checkpointLagMs?: number;
222
+ now?: () => number;
223
+ /**
224
+ * QuestDB's confirmation that rows can be read back, consulted before a
225
+ * position is saved. Without it a position rests on the writer's counters
226
+ * and the lag alone.
227
+ */
228
+ confirmStored?: ConfirmStored;
128
229
  }): Promise<void>;
230
+ /**
231
+ * The Signal K context a row is written under, or null for a row that is left
232
+ * out — counted as skipped by the caller.
233
+ *
234
+ * The source's own vessel becomes `self`, whether the point was tagged with
235
+ * the source's own context, with the literal `vessels.self`, or not at all.
236
+ * Any other tag is another vessel: kept under its own context if it is a
237
+ * well-formed Signal K one, since that is what the history API is queried by,
238
+ * or left out when other vessels are not wanted. With no `sourceSelf` given,
239
+ * every row is the own vessel — what a single-vessel source amounts to.
240
+ */
241
+ export declare function targetContext(rowContext: string | undefined, sourceSelf: string | undefined, self: string, others: "keep" | "skip"): string | null;
129
242
  /** One point read out of InfluxDB, normalised across the two dialects. */
130
243
  export interface SourceRow {
131
244
  tsNanos: bigint;
132
245
  field: string;
133
246
  value: unknown;
247
+ /** The source's `context` tag, when the point carried one. */
248
+ context?: string;
134
249
  }
135
250
  /**
136
251
  * Recombine latitude/longitude fields of a position measurement into one
137
252
  * object value at the same instant.
138
253
  *
139
- * The Signal K InfluxDB plugins store navigation.position as two fields
140
- * (`latitude`/`longitude`, or `lat`/`lon`). Left as-is they would import as
141
- * two numeric paths and the position history would be unusable — QuestDB keeps
142
- * positions in their own table.
254
+ * signalk-to-influxdb2 stores navigation.position as two fields (`lat`/`lon`),
255
+ * as does signalk-to-influxdb 1.x with `separateLatLon` on. Left as-is they
256
+ * would import as two numeric paths and the position history would be unusable
257
+ * — QuestDB keeps positions in their own table. The 1.x default encoding, a
258
+ * single `jsonValue`, is already whole by the time it gets here (see
259
+ * decodeJsonValueRows).
143
260
  */
144
261
  export declare function mergePositionRows(measurement: string, rows: SourceRow[]): {
145
262
  rows: SourceRow[];