@mxtommy/kip 4.6.0-beta.2 → 4.7.0-beta.10

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/CHANGELOG.md +12 -3
  2. package/package.json +17 -17
  3. package/plugin/history-series.service.js +125 -17
  4. package/plugin/index.js +168 -54
  5. package/plugin/kip-series-contract.js +14 -0
  6. package/plugin/openApi.json +90 -2
  7. package/plugin/sqlite-history-storage.service.js +14 -20
  8. package/public/3rdpartylicenses.txt +36 -0
  9. package/public/assets/help-docs/dashboards.md +3 -3
  10. package/public/assets/help-docs/datainspector.md +3 -3
  11. package/public/assets/help-docs/history-api.md +31 -21
  12. package/public/assets/help-docs/menu.json +1 -1
  13. package/public/assets/help-docs/nodered-control-flows.md +4 -4
  14. package/public/assets/help-docs/putcontrols.md +6 -6
  15. package/public/assets/help-docs/widget-historical-series.md +56 -12
  16. package/public/assets/help-docs/zones.md +1 -1
  17. package/public/assets/svg/icons.svg +17 -0
  18. package/public/{chunk-P7JKENHI.js → chunk-2GOHQZH5.js} +4 -4
  19. package/public/{chunk-NFJ4RQSE.js → chunk-4YDVZHMH.js} +4 -4
  20. package/public/{chunk-FZSLNGBK.js → chunk-6U74K6G4.js} +7 -7
  21. package/public/{chunk-TVNXBPFF.js → chunk-AQROQY2F.js} +1 -1
  22. package/public/{chunk-XBSU7OGT.js → chunk-AZC2WKQI.js} +1 -1
  23. package/public/{chunk-WH5CIUSB.js → chunk-BGGO4PGD.js} +1 -1
  24. package/public/{chunk-R36UY4Q4.js → chunk-BQPPRM7O.js} +1 -1
  25. package/public/{chunk-BEQKBGLG.js → chunk-BTVGQ4ZG.js} +2 -2
  26. package/public/{chunk-RCYOZLZB.js → chunk-CSIELI2Z.js} +2 -2
  27. package/public/{chunk-VXCYPAWR.js → chunk-FYDLTNP4.js} +1 -1
  28. package/public/{chunk-YI3MZWRZ.js → chunk-HSKVTFFQ.js} +1 -1
  29. package/public/{chunk-TBNKOU7M.js → chunk-IENESD5Q.js} +1 -1
  30. package/public/chunk-LS6AJ3JI.js +50 -0
  31. package/public/{chunk-SJFJEOSG.js → chunk-M37BLWHF.js} +5 -5
  32. package/public/chunk-MXUEYEZU.js +5 -0
  33. package/public/{chunk-WQSJFJLW.js → chunk-POMIQBAL.js} +2 -2
  34. package/public/{chunk-P4CRTB7N.js → chunk-PTLDR7X7.js} +1 -1
  35. package/public/{chunk-OPTBDYBL.js → chunk-PUPM3HUQ.js} +1 -1
  36. package/public/chunk-PZ6I6W3H.js +16 -0
  37. package/public/{chunk-Q2ANAJAD.js → chunk-SUWMN3AE.js} +1 -1
  38. package/public/{chunk-FZFDGAQO.js → chunk-WJFXI5PQ.js} +1 -1
  39. package/public/{chunk-BJEHRCYP.js → chunk-X44BRNVL.js} +1 -1
  40. package/public/{chunk-KWTS7JF7.js → chunk-Y6JCNR3H.js} +1 -1
  41. package/public/{chunk-VPF5756E.js → chunk-YY4ZUJFI.js} +5 -5
  42. package/public/{chunk-J6EEFXKZ.js → chunk-Z4K5KE3I.js} +8 -8
  43. package/public/index.html +1 -1
  44. package/public/{main-TZOV3JCT.js → main-775NFBN3.js} +1 -1
  45. package/public/chunk-67V4XHCY.js +0 -5
  46. package/public/chunk-BTFZS2TW.js +0 -16
  47. package/public/chunk-RFNZ4AQG.js +0 -50
package/CHANGELOG.md CHANGED
@@ -1,12 +1,21 @@
1
- # v4.5.2
1
+ # v4.6.0
2
+ ## Improvements
3
+ * Built-in Time-Series storage and History-API provider now use the native node:sqlite feature, eliminating binary and external dependencies.
4
+ * Requires Node.js 22.5.0 or newer. If you use an older Node.js version, you must select an alternative History-API provider.
5
+ * **IMPORTANT:** Before upgrading Node.js, always confirm your Signal K server and OS supports the required Node.js version. See the [Signal K installation documentation](https://demo.signalk.org/documentation/Installation.html).
6
+ ## Fixes
7
+ * Extending v4.5.x features to VenusOS (32bit OS) - Error: Failed to start: Error loading duckdb native binding: unsupported arch 'arm' for platform 'linux'. Fixes #979
8
+ * Uninstallation does not remove all files. Fixes #981
9
+ * Reduce unwarranted installation size.
10
+ # v4.5.2 - Deprecated version due to lack of VenusOS (32bit) DuckDB binary support
2
11
  ## Fixes
3
12
  * DuckDB initialized when features are not enabled.
4
13
  * Parquet data compression and pruning not executing.
5
- # v4.5.1
14
+ # v4.5.1 - Deprecated version due to lack of VenusOS (32bit) DuckDB binary support
6
15
  ## Fixes
7
16
  * DuckDB dependency causing build and installation errors. Fixes #979
8
17
  * Reduced installation size. Fixes #980
9
- # v4.5.0
18
+ # v4.5.0 - Deprecated version due to lack of VenusOS (32bit) DuckDB binary support
10
19
  ## New Features
11
20
  * Effortlessly review your vessel’s history with the new Widget Historical Charts—automatically track, store, and visualize key data. Instantly access up to the last full day of performance: just two-finger tap or right-click any widget to open a seamless history dialog—no setup, no clutter, just the trends you need. (Requires Signal K v2.22.1)
12
21
  * Dashboards now start with fully populated Data Charts, powered by KIP’s managed Time-Series History-API provider or other compatible history providers. (Requires Signal K v2.22.1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mxtommy/kip",
3
- "version": "4.6.0-beta.2",
3
+ "version": "4.7.0-beta.10",
4
4
  "description": "An advanced and versatile marine instrumentation package to display Signal K data.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -66,22 +66,22 @@
66
66
  },
67
67
  "schematics": "tools/schematics/collection.json",
68
68
  "devDependencies": {
69
- "@angular/cdk": "21.2.0",
70
- "@angular/common": "21.2.0",
71
- "@angular/compiler": "21.2.0",
72
- "@angular/core": "21.2.0",
73
- "@angular/forms": "21.2.0",
74
- "@angular/material": "21.2.0",
75
- "@angular/animations": "21.2.0",
76
- "@angular/platform-browser": "21.2.0",
77
- "@angular/platform-browser-dynamic": "21.2.0",
78
- "@angular/router": "21.2.0",
79
- "@angular-devkit/build-angular": "^21.1.4",
69
+ "@angular/cdk": "21.2.1",
70
+ "@angular/common": "21.2.1",
71
+ "@angular/compiler": "21.2.1",
72
+ "@angular/core": "21.2.1",
73
+ "@angular/forms": "21.2.1",
74
+ "@angular/material": "21.2.1",
75
+ "@angular/animations": "21.2.1",
76
+ "@angular/platform-browser": "21.2.1",
77
+ "@angular/platform-browser-dynamic": "21.2.1",
78
+ "@angular/router": "21.2.1",
79
+ "@angular-devkit/build-angular": "^21.2.1",
80
80
  "@angular-devkit/schematics-cli": "^20.1.6",
81
- "@angular/build": "^21.1.4",
82
- "@angular/cli": "^21.1.4",
83
- "@angular/compiler-cli": "21.2.0",
84
- "@angular/language-service": "21.2.0",
81
+ "@angular/build": "^21.2.1",
82
+ "@angular/cli": "^21.2.1",
83
+ "@angular/compiler-cli": "21.2.1",
84
+ "@angular/language-service": "21.2.1",
85
85
  "@types/canvas-gauges": "^2.1.8",
86
86
  "@types/d3": "^7.4.3",
87
87
  "@types/jasmine": "~3.6.0",
@@ -89,7 +89,7 @@
89
89
  "@types/js-quantities": "^1.6.6",
90
90
  "@types/lodash-es": "^4.17.9",
91
91
  "@types/node": "^24.1.0",
92
- "angular-eslint": "21.2.0",
92
+ "angular-eslint": "21.3.0",
93
93
  "codelyzer": "^6.0.0",
94
94
  "eslint": "^9.29.0",
95
95
  "jasmine-core": "~4.0.1",
@@ -1,16 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.HistorySeriesService = void 0;
3
+ exports.HistorySeriesService = exports.isKipTemplateSeriesDefinition = exports.isKipSeriesEnabled = exports.isKipConcreteSeriesDefinition = void 0;
4
+ const kip_series_contract_1 = require("./kip-series-contract");
5
+ Object.defineProperty(exports, "isKipConcreteSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipConcreteSeriesDefinition; } });
6
+ Object.defineProperty(exports, "isKipSeriesEnabled", { enumerable: true, get: function () { return kip_series_contract_1.isKipSeriesEnabled; } });
7
+ Object.defineProperty(exports, "isKipTemplateSeriesDefinition", { enumerable: true, get: function () { return kip_series_contract_1.isKipTemplateSeriesDefinition; } });
4
8
  /**
5
9
  * Manages history capture series definitions and serves History API-compatible query results.
6
10
  */
7
11
  class HistorySeriesService {
8
12
  nowProvider;
13
+ selfContext;
9
14
  seriesById = new Map();
15
+ enabledSeriesKeysByPath = new Map();
10
16
  lastAcceptedTimestampBySeriesKey = new Map();
11
17
  sampleSink = null;
12
- constructor(nowProvider = () => Date.now()) {
18
+ constructor(nowProvider = () => Date.now(), selfContext = null) {
13
19
  this.nowProvider = nowProvider;
20
+ this.selfContext = selfContext;
14
21
  }
15
22
  /**
16
23
  * Returns all configured series sorted by `seriesId`.
@@ -56,6 +63,7 @@ class HistorySeriesService {
56
63
  const normalized = this.normalizeSeries(input);
57
64
  const key = normalized.seriesId;
58
65
  this.seriesById.set(key, normalized);
66
+ this.rebuildEnabledPathIndex();
59
67
  return normalized;
60
68
  }
61
69
  /**
@@ -90,6 +98,7 @@ class HistorySeriesService {
90
98
  this.seriesById.delete(key);
91
99
  this.lastAcceptedTimestampBySeriesKey.delete(key);
92
100
  });
101
+ this.rebuildEnabledPathIndex();
93
102
  return true;
94
103
  }
95
104
  /**
@@ -103,7 +112,7 @@ class HistorySeriesService {
103
112
  * console.log(result.created, result.deleted);
104
113
  */
105
114
  reconcileSeries(desiredSeries) {
106
- const now = Date.now();
115
+ const now = this.nowProvider();
107
116
  const desiredById = new Map();
108
117
  desiredSeries.forEach(entry => {
109
118
  const normalized = this.normalizeSeries(entry);
@@ -122,7 +131,7 @@ class HistorySeriesService {
122
131
  created += 1;
123
132
  return;
124
133
  }
125
- if (JSON.stringify(existing) !== JSON.stringify(desired)) {
134
+ if (!this.areSeriesEquivalent(existing, desired)) {
126
135
  this.seriesById.set(seriesKey, desiredWithReconcile);
127
136
  updated += 1;
128
137
  }
@@ -138,6 +147,7 @@ class HistorySeriesService {
138
147
  deleted += 1;
139
148
  }
140
149
  });
150
+ this.rebuildEnabledPathIndex();
141
151
  return {
142
152
  created,
143
153
  updated,
@@ -157,12 +167,10 @@ class HistorySeriesService {
157
167
  * service.recordSample('abc', 12.4, Date.now());
158
168
  */
159
169
  recordSample(seriesId, value, timestamp) {
160
- const seriesEntry = Array.from(this.seriesById.entries())
161
- .find(([, series]) => series.seriesId === seriesId);
162
- if (!seriesEntry) {
170
+ if (!this.seriesById.has(seriesId)) {
163
171
  return false;
164
172
  }
165
- return this.recordSampleByKey(seriesEntry[0], value, timestamp);
173
+ return this.recordSampleByKey(seriesId, value, timestamp);
166
174
  }
167
175
  recordSampleByKey(seriesKey, value, timestamp) {
168
176
  const series = this.seriesById.get(seriesKey);
@@ -213,8 +221,13 @@ class HistorySeriesService {
213
221
  }
214
222
  let recorded = 0;
215
223
  leafSamples.forEach(leaf => {
216
- this.seriesById.forEach((series, seriesKey) => {
217
- if (series.path !== leaf.path || series.enabled === false) {
224
+ const seriesKeys = this.enabledSeriesKeysByPath.get(leaf.path);
225
+ if (!seriesKeys || seriesKeys.length === 0) {
226
+ return;
227
+ }
228
+ seriesKeys.forEach(seriesKey => {
229
+ const series = this.seriesById.get(seriesKey);
230
+ if (!series) {
218
231
  return;
219
232
  }
220
233
  const seriesContext = series.context ?? 'vessels.self';
@@ -293,6 +306,68 @@ class HistorySeriesService {
293
306
  });
294
307
  return Array.from(contexts).sort();
295
308
  }
309
+ rebuildEnabledPathIndex() {
310
+ this.enabledSeriesKeysByPath.clear();
311
+ this.seriesById.forEach((series, seriesKey) => {
312
+ if (series.enabled === false) {
313
+ return;
314
+ }
315
+ const keys = this.enabledSeriesKeysByPath.get(series.path) ?? [];
316
+ keys.push(seriesKey);
317
+ this.enabledSeriesKeysByPath.set(series.path, keys);
318
+ });
319
+ }
320
+ areSeriesEquivalent(left, right) {
321
+ const leftComparable = this.toComparableSeries(left);
322
+ const rightComparable = this.toComparableSeries(right);
323
+ return leftComparable.seriesId === rightComparable.seriesId
324
+ && leftComparable.datasetUuid === rightComparable.datasetUuid
325
+ && leftComparable.ownerWidgetUuid === rightComparable.ownerWidgetUuid
326
+ && leftComparable.ownerWidgetSelector === rightComparable.ownerWidgetSelector
327
+ && leftComparable.path === rightComparable.path
328
+ && leftComparable.expansionMode === rightComparable.expansionMode
329
+ && this.areStringArraysEquivalent(leftComparable.allowedBatteryIds, rightComparable.allowedBatteryIds)
330
+ && leftComparable.source === rightComparable.source
331
+ && leftComparable.context === rightComparable.context
332
+ && leftComparable.timeScale === rightComparable.timeScale
333
+ && leftComparable.period === rightComparable.period
334
+ && leftComparable.retentionDurationMs === rightComparable.retentionDurationMs
335
+ && leftComparable.sampleTime === rightComparable.sampleTime
336
+ && leftComparable.enabled === rightComparable.enabled
337
+ && this.areStringArraysEquivalent(leftComparable.methods, rightComparable.methods);
338
+ }
339
+ toComparableSeries(series) {
340
+ const { reconcileTs, ...comparable } = series;
341
+ void reconcileTs;
342
+ return {
343
+ ...comparable,
344
+ allowedBatteryIds: this.normalizeComparableStringArray(comparable.allowedBatteryIds),
345
+ methods: this.normalizeComparableStringArray(comparable.methods)
346
+ };
347
+ }
348
+ areStringArraysEquivalent(left, right) {
349
+ const normalizedLeft = this.normalizeComparableStringArray(left) ?? [];
350
+ const normalizedRight = this.normalizeComparableStringArray(right) ?? [];
351
+ if (normalizedLeft.length !== normalizedRight.length) {
352
+ return false;
353
+ }
354
+ return normalizedLeft.every((value, index) => value === normalizedRight[index]);
355
+ }
356
+ normalizeComparableStringArray(values) {
357
+ if (!Array.isArray(values) || values.length === 0) {
358
+ return undefined;
359
+ }
360
+ return [...values]
361
+ .filter((value) => typeof value === 'string')
362
+ .sort((left, right) => left.localeCompare(right));
363
+ }
364
+ isChartWidget(ownerWidgetSelector, ownerWidgetUuid) {
365
+ if (ownerWidgetSelector === 'widget-data-chart' || ownerWidgetSelector === 'widget-windtrends-chart') {
366
+ return true;
367
+ }
368
+ return ownerWidgetUuid?.startsWith('widget-windtrends-chart') === true
369
+ || ownerWidgetUuid?.startsWith('widget-data-chart') === true;
370
+ }
296
371
  normalizeSeries(input) {
297
372
  const seriesId = (input.seriesId || input.datasetUuid || '').trim();
298
373
  if (!seriesId) {
@@ -310,8 +385,16 @@ class HistorySeriesService {
310
385
  if (!path) {
311
386
  throw new Error('path is required');
312
387
  }
313
- // Determine if this is a chart type widget
314
- const isDataWidget = ownerWidgetUuid?.startsWith('widget-windtrends-chart') || ownerWidgetUuid?.startsWith('widget-data-chart');
388
+ const ownerWidgetSelector = typeof input.ownerWidgetSelector === 'string' ? input.ownerWidgetSelector.trim() : null;
389
+ const expansionMode = input.expansionMode ?? null;
390
+ if (expansionMode === 'bms-battery-tree' && ownerWidgetSelector !== 'widget-bms') {
391
+ throw new Error('BMS template series must use ownerWidgetSelector "widget-bms"');
392
+ }
393
+ const normalizedMethods = this.normalizeComparableStringArray(input.methods);
394
+ const normalizedAllowedBatteryIds = expansionMode === 'bms-battery-tree'
395
+ ? this.normalizeComparableStringArray(input.allowedBatteryIds)
396
+ : undefined;
397
+ const isDataWidget = this.isChartWidget(ownerWidgetSelector, ownerWidgetUuid);
315
398
  const retentionMs = this.resolveRetentionMs(input);
316
399
  let sampleTime;
317
400
  if (isDataWidget) {
@@ -324,18 +407,37 @@ class HistorySeriesService {
324
407
  // a good median amount of samples for the dynamically queryable chart display range (15 min up to 24h).
325
408
  sampleTime = 15000; // ms
326
409
  }
327
- return {
328
- ...input,
410
+ const normalizedBase = {
329
411
  seriesId,
330
412
  datasetUuid,
331
413
  ownerWidgetUuid,
414
+ ownerWidgetSelector,
332
415
  path,
333
416
  source: input.source ?? 'default',
334
417
  context: input.context ?? 'vessels.self',
418
+ timeScale: input.timeScale ?? null,
419
+ period: Number.isFinite(input.period) ? input.period ?? null : null,
335
420
  enabled: input.enabled !== false,
336
421
  retentionDurationMs: retentionMs,
337
- sampleTime
422
+ sampleTime,
423
+ methods: normalizedMethods,
424
+ reconcileTs: input.reconcileTs
338
425
  };
426
+ if (expansionMode === 'bms-battery-tree') {
427
+ const templateSeries = {
428
+ ...normalizedBase,
429
+ ownerWidgetSelector: 'widget-bms',
430
+ expansionMode,
431
+ allowedBatteryIds: normalizedAllowedBatteryIds ?? null
432
+ };
433
+ return templateSeries;
434
+ }
435
+ const concreteSeries = {
436
+ ...normalizedBase,
437
+ expansionMode: null,
438
+ allowedBatteryIds: null
439
+ };
440
+ return concreteSeries;
339
441
  }
340
442
  resolveRetentionMs(series) {
341
443
  if (Number.isFinite(series.retentionDurationMs) && series.retentionDurationMs > 0) {
@@ -408,11 +510,17 @@ class HistorySeriesService {
408
510
  if (seriesContext === sampleContext) {
409
511
  return true;
410
512
  }
411
- if (seriesContext === 'vessels.self') {
412
- return sampleContext === 'vessels.self' || sampleContext.startsWith('vessels.');
513
+ if (this.isSelfContext(seriesContext) && this.isSelfContext(sampleContext)) {
514
+ return true;
413
515
  }
414
516
  return false;
415
517
  }
518
+ isSelfContext(context) {
519
+ if (context === 'vessels.self') {
520
+ return true;
521
+ }
522
+ return !!this.selfContext && context === this.selfContext;
523
+ }
416
524
  isSourceMatch(seriesSource, sampleSource) {
417
525
  if (seriesSource === '*' || seriesSource === 'any') {
418
526
  return true;