@mxtommy/kip 4.5.0 → 4.5.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 (71) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/package.json +16 -6
  3. package/plugin/duckdb-parquet-storage.service.js +78 -102
  4. package/plugin/index.js +60 -40
  5. package/public/{chunk-6XFWUUDD.js → chunk-EQ2N7KDA.js} +1 -1
  6. package/public/{chunk-EDNYYQIZ.js → chunk-IYRLINL7.js} +1 -1
  7. package/public/{chunk-UYIJND2R.js → chunk-JGGMFMY5.js} +1 -1
  8. package/public/{chunk-DD4F6F4S.js → chunk-RONXIZ2U.js} +8 -8
  9. package/public/{chunk-2ICAVOT2.js → chunk-VCY32MWT.js} +1 -1
  10. package/public/{chunk-J3LDKVIS.js → chunk-ZV7IYYEQ.js} +1 -1
  11. package/public/index.html +1 -1
  12. package/public/{main-EG2WF4EO.js → main-FQESQQV6.js} +1 -1
  13. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -84
  14. package/.github/ISSUE_TEMPLATE/config.yml +0 -5
  15. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -35
  16. package/.github/copilot-instructions.md +0 -218
  17. package/.github/instructions/angular.instructions.md +0 -123
  18. package/.github/instructions/best-practices.instructions.md +0 -59
  19. package/.github/instructions/project.instructions.md +0 -468
  20. package/.github/workflows/ci.yml +0 -37
  21. package/docs/widget-schematic.md +0 -102
  22. package/images/ActionSidenav.png +0 -0
  23. package/images/ChartplotterMode.png +0 -0
  24. package/images/KIPDemo.png +0 -0
  25. package/images/KipBrightness-1024.png +0 -0
  26. package/images/KipConfig-Units-1024.png +0 -0
  27. package/images/KipConfig-display-1024x488.png +0 -0
  28. package/images/KipFreeboard-SK-1024.png +0 -0
  29. package/images/KipGaugeSample1-1024x545.png +0 -0
  30. package/images/KipGaugeSample2-1024x488.png +0 -0
  31. package/images/KipGaugeSample3-1024x508.png +0 -0
  32. package/images/KipNightMode-1024.png +0 -0
  33. package/images/KipWidgetConfig-layout-1024.png +0 -0
  34. package/images/KipWidgetConfig-paths-1024x488.png +0 -0
  35. package/images/Options.png +0 -0
  36. package/images/exterior_user_installs.png +0 -0
  37. package/images/formfactor.png +0 -0
  38. package/plugin-config-data/kip/historicalData/kip-history.duckdb +0 -0
  39. package/plugin-config-data/kip/historicalData/parquet/chart-1/1772344583976-1772344583976.parquet +0 -0
  40. package/plugin-config-data/kip/historicalData/parquet/live-1/1771408800000-1771408890000.parquet +0 -0
  41. package/plugin-config-data/kip/historicalData/parquet/live-1/1771412400000-1771412490000.parquet +0 -0
  42. package/plugin-config-data/kip/historicalData/parquet/live-1/1771419600000-1771419650000.parquet +0 -0
  43. package/plugin-config-data/kip/historicalData/parquet/live-1/1772344584154-1772344584154.parquet +0 -0
  44. package/plugin-config-data/kip/historicalData/parquet/live-1/1772344584191-1772344584191.parquet +0 -0
  45. package/plugin-config-data/kip/historicalData/parquet/live-1/1772344584268-1772344584268.parquet +0 -0
  46. package/plugin-config-data/kip/historicalData/parquet/live-2/1771502400000-1771502400000.parquet +0 -0
  47. package/plugin-config-data/kip/historicalData/parquet/live-3/1771408800000-1771408890000.parquet +0 -0
  48. package/plugin-config-data/kip/historicalData/parquet/live-3/1771412400000-1771412490000.parquet +0 -0
  49. package/plugin-config-data/kip/historicalData/parquet/live-3/1771419600000-1771419650000.parquet +0 -0
  50. package/plugin-config-data/kip/historicalData/parquet/live-3/1772344584268-1772344584268.parquet +0 -0
  51. package/plugin-config-data/kip/historicalData/parquet/live-4/1771408800000-1771408890000.parquet +0 -0
  52. package/plugin-config-data/kip/historicalData/parquet/live-4/1771412400000-1771412490000.parquet +0 -0
  53. package/plugin-config-data/kip/historicalData/parquet/live-4/1771419600000-1771419650000.parquet +0 -0
  54. package/plugin-config-data/kip/historicalData/parquet/live-5/1771412400000-1771412490000.parquet +0 -0
  55. package/plugin-config-data/kip/historicalData/parquet/live-5/1771419600000-1771419650000.parquet +0 -0
  56. package/plugin-config-data/kip/historicalData/parquet/live-6/1771419600000-1771419650000.parquet +0 -0
  57. package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1771408800000-1771408890000.parquet +0 -0
  58. package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1771412400000-1771412490000.parquet +0 -0
  59. package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1771419600000-1771419650000.parquet +0 -0
  60. package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1772344584191-1772344584191.parquet +0 -0
  61. package/plugin-config-data/kip/historicalData/parquet/live-prefixed-1/1772344584268-1772344584268.parquet +0 -0
  62. package/tools/schematics/collection.json +0 -9
  63. package/tools/schematics/create-host2-widget/files/readme/README.md.template +0 -109
  64. package/tools/schematics/create-host2-widget/files/spec/widget-__name@dasherize__.component.spec.ts +0 -38
  65. package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.html +0 -6
  66. package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.scss +0 -5
  67. package/tools/schematics/create-host2-widget/files/widget/widget-__name@dasherize__.component.ts.template +0 -94
  68. package/tools/schematics/create-host2-widget/index.js +0 -138
  69. package/tools/schematics/create-host2-widget/schema.json +0 -89
  70. package/tools/schematics/create-host2-widget/test/create-host2-widget.spec.ts +0 -70
  71. package/tools/schematics/create-host2-widget/utils/formatting.js +0 -119
package/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ # v4.5.1
2
+ ## Fixes
3
+ * DuckDB dependency causing build and installation errors. Fixes #979
4
+ * Reduced installation size. Fixes #980
1
5
  # v4.5.0
2
6
  ## New Features
3
7
  * 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)
@@ -7,6 +11,7 @@
7
11
  * Added "Days" as a selectable time scale in the Data Chart widget.
8
12
  * Improved integration by validating server plugin presence, plugin state, and configuration.
9
13
  * Added a Node-RED introduction guide to the Help section.
14
+ * Migrated KIP plugin historical storage internals from `duckdb` to `@duckdb/node-api` and Parquet export writing to `@dsnp/parquetjs`.
10
15
  ## Fixes
11
16
  * Improved KIP plugin OpenAPI compatibility.
12
17
  * Resolved slow Data Inspector performance caused by high resource usage in deep loop logic.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mxtommy/kip",
3
- "version": "4.5.0",
3
+ "version": "4.5.1",
4
4
  "description": "An advanced and versatile marine instrumentation package to display Signal K data.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -40,6 +40,15 @@
40
40
  "displayName": "KIP Instrument MFD"
41
41
  },
42
42
  "main": "plugin/index.js",
43
+ "files": [
44
+ "plugin/**",
45
+ "public/**",
46
+ "README.md",
47
+ "LICENSE",
48
+ "CHANGELOG.md",
49
+ "CONTRIBUTORS.md",
50
+ "package.json"
51
+ ],
43
52
  "signalk-plugin-enabled-by-default": true,
44
53
  "scripts": {
45
54
  "test": "CI=1 ng test --watch=false",
@@ -87,9 +96,7 @@
87
96
  "pwa-asset-generator": "^8.1.1",
88
97
  "sass": "^1.49.9",
89
98
  "ts-node": "^10.9.2",
90
- "typescript": "^5.9.3"
91
- },
92
- "dependencies": {
99
+ "typescript": "^5.9.3",
93
100
  "@angular/animations": "21.1.4",
94
101
  "@angular/cdk": "21.1.4",
95
102
  "@angular/common": "21.1.4",
@@ -102,7 +109,6 @@
102
109
  "@angular/router": "21.1.4",
103
110
  "@aziham/chartjs-plugin-streaming": "^3.5.1",
104
111
  "@godind/ng-canvas-gauges": "^6.2.1",
105
- "@signalk/server-api": "^2.22.0",
106
112
  "@zakj/no-sleep": "^0.13.5",
107
113
  "chart.js": "^4.5.1",
108
114
  "chartjs-adapter-date-fns": "^3.0.0",
@@ -112,7 +118,6 @@
112
118
  "core-js": "^3.13.1",
113
119
  "d3": "^7.9.0",
114
120
  "date-fns": "^2.30.0",
115
- "duckdb": "^1.4.4",
116
121
  "gridstack": "^12.3.3",
117
122
  "js-quantities": "^1.8.0",
118
123
  "lodash-es": "^4.17.23",
@@ -124,5 +129,10 @@
124
129
  "steelseries": "^2.0.9",
125
130
  "tslib": "^2.6.2",
126
131
  "zone.js": "^0.15.1"
132
+ },
133
+ "dependencies": {
134
+ "@dsnp/parquetjs": "^1.8.7",
135
+ "@duckdb/node-api": "^1.4.4-r.2",
136
+ "@signalk/server-api": "^2.22.0"
127
137
  }
128
138
  }
@@ -1,42 +1,10 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
3
  exports.DuckDbParquetStorageService = void 0;
37
4
  const fs_1 = require("fs");
38
5
  const path_1 = require("path");
39
- const duckdb = __importStar(require("duckdb"));
6
+ const node_api_1 = require("@duckdb/node-api");
7
+ const parquetjs_1 = require("@dsnp/parquetjs");
40
8
  /**
41
9
  * Provides DuckDB storage and Parquet flush support for captured history samples.
42
10
  */
@@ -83,17 +51,15 @@ class DuckDbParquetStorageService {
83
51
  this.logger = logger;
84
52
  }
85
53
  /**
86
- * Applies plugin settings into the storage backend configuration.
54
+ * Applies the fixed storage backend configuration.
87
55
  *
88
- * @param {unknown} settings Plugin settings payload from Signal K (ignored for fixed storage defaults).
89
56
  * @returns {IDuckDbParquetStorageConfig} Fixed storage configuration.
90
57
  *
91
58
  * @example
92
- * const cfg = storage.configure({});
59
+ * const cfg = storage.configure();
93
60
  * console.log(cfg.engine);
94
61
  */
95
- configure(settings) {
96
- void settings;
62
+ configure() {
97
63
  this.initialized = false;
98
64
  this.config = {
99
65
  engine: 'duckdb-parquet',
@@ -118,21 +84,11 @@ class DuckDbParquetStorageService {
118
84
  this.initialized = false;
119
85
  this.lifecycleToken += 1;
120
86
  try {
121
- const duckdbModule = duckdb;
122
87
  const dbPath = (0, path_1.resolve)(this.config.databaseFile);
123
88
  (0, fs_1.mkdirSync)((0, path_1.dirname)(dbPath), { recursive: true });
124
89
  (0, fs_1.mkdirSync)((0, path_1.resolve)(this.config.parquetDirectory), { recursive: true });
125
- this.db = new duckdbModule.Database(dbPath);
126
- const maybeConnection = typeof this.db.connect === 'function'
127
- ? this.db.connect()
128
- : this.db;
129
- if (!maybeConnection
130
- || typeof maybeConnection.run !== 'function'
131
- || typeof maybeConnection.all !== 'function'
132
- || typeof maybeConnection.close !== 'function') {
133
- throw new Error('DuckDB connection API is unavailable in this runtime');
134
- }
135
- this.connection = maybeConnection;
90
+ this.db = await node_api_1.DuckDBInstance.create(dbPath);
91
+ this.connection = await this.db.connect();
136
92
  await this.createCoreTables();
137
93
  await this.runSql('CREATE INDEX IF NOT EXISTS idx_history_series_scope_ts ON history_samples(series_id, ts_ms)');
138
94
  await this.runSql('CREATE INDEX IF NOT EXISTS idx_history_series_scope_id ON history_series(series_id)');
@@ -154,7 +110,7 @@ class DuckDbParquetStorageService {
154
110
  const message = error?.message ?? String(error);
155
111
  this.lastInitError = message;
156
112
  this.logger.error(`[SERIES STORAGE] DuckDB initialization failed: ${message}`);
157
- this.logger.error('[SERIES STORAGE] DuckDB is required. Install runtime dependency with: npm i duckdb in the installed plugin directory, then restart Signal K.');
113
+ this.logger.error('[SERIES STORAGE] DuckDB Node API is required. Install runtime dependency with: npm i @duckdb/node-api in the installed plugin directory, then restart Signal K.');
158
114
  this.connection = null;
159
115
  this.db = null;
160
116
  this.pendingRows = [];
@@ -811,10 +767,20 @@ class DuckDbParquetStorageService {
811
767
  return;
812
768
  }
813
769
  const connection = this.connection;
770
+ const db = this.db;
814
771
  this.connection = null;
815
- await new Promise((resolvePromise) => {
816
- connection.close(() => resolvePromise());
817
- });
772
+ try {
773
+ connection.disconnectSync();
774
+ }
775
+ catch {
776
+ // ignore disconnect failures during shutdown
777
+ }
778
+ try {
779
+ db?.closeSync();
780
+ }
781
+ catch {
782
+ // ignore close failures during shutdown
783
+ }
818
784
  this.db = null;
819
785
  }
820
786
  async createCoreTables() {
@@ -850,10 +816,6 @@ class DuckDbParquetStorageService {
850
816
  )
851
817
  `);
852
818
  }
853
- async countRows(tableName) {
854
- const rows = await this.querySql(`SELECT COUNT(*) AS removed_rows FROM ${tableName}`);
855
- return this.toNumberOrUndefined(rows[0]?.removed_rows) ?? 0;
856
- }
857
819
  async insertRows(rows) {
858
820
  if (rows.length === 0) {
859
821
  return;
@@ -880,56 +842,73 @@ class DuckDbParquetStorageService {
880
842
  const seriesDir = (0, path_1.join)(baseDir, this.safePath(seriesId));
881
843
  (0, fs_1.mkdirSync)(seriesDir, { recursive: true });
882
844
  const filePath = (0, path_1.join)(seriesDir, `${fromMs}-${toMs}.parquet`);
883
- const escapedSeries = this.escape(seriesId);
884
- const escapedFile = this.escapePath(filePath);
885
- const sql = `
886
- COPY (
887
- SELECT
888
- series_id,
889
- dataset_uuid,
890
- owner_widget_uuid,
891
- path,
892
- context,
893
- source,
894
- ts_ms,
895
- to_timestamp(ts_ms / 1000.0) AS ts,
896
- value
897
- FROM history_samples
898
- WHERE series_id = ${escapedSeries}
899
- AND ts_ms >= ${Math.trunc(fromMs)}
900
- AND ts_ms <= ${Math.trunc(toMs)}
901
- ORDER BY ts_ms
902
- ) TO '${escapedFile}' (FORMAT PARQUET)
903
- `;
904
- await this.runSql(sql);
845
+ const rows = await this.querySql(`
846
+ SELECT
847
+ series_id,
848
+ dataset_uuid,
849
+ owner_widget_uuid,
850
+ path,
851
+ context,
852
+ source,
853
+ ts_ms,
854
+ value
855
+ FROM history_samples
856
+ WHERE series_id = ${this.escape(seriesId)}
857
+ AND ts_ms >= ${Math.trunc(fromMs)}
858
+ AND ts_ms <= ${Math.trunc(toMs)}
859
+ ORDER BY ts_ms
860
+ `);
861
+ if (rows.length === 0) {
862
+ return;
863
+ }
864
+ const schema = new parquetjs_1.ParquetSchema({
865
+ series_id: { type: 'UTF8' },
866
+ dataset_uuid: { type: 'UTF8' },
867
+ owner_widget_uuid: { type: 'UTF8' },
868
+ path: { type: 'UTF8' },
869
+ context: { type: 'UTF8' },
870
+ source: { type: 'UTF8', optional: true },
871
+ ts_ms: { type: 'INT64' },
872
+ ts: { type: 'TIMESTAMP_MILLIS' },
873
+ value: { type: 'DOUBLE' }
874
+ });
875
+ const writer = await parquetjs_1.ParquetWriter.openFile(schema, filePath);
876
+ try {
877
+ for (const row of rows) {
878
+ const timestampMs = this.toNumberOrUndefined(row.ts_ms);
879
+ const numericValue = this.toNumberOrUndefined(row.value);
880
+ if (timestampMs === undefined || numericValue === undefined) {
881
+ continue;
882
+ }
883
+ await writer.appendRow({
884
+ series_id: row.series_id,
885
+ dataset_uuid: row.dataset_uuid,
886
+ owner_widget_uuid: row.owner_widget_uuid,
887
+ path: row.path,
888
+ context: row.context,
889
+ source: row.source ?? undefined,
890
+ ts_ms: Math.trunc(timestampMs),
891
+ ts: new Date(timestampMs),
892
+ value: numericValue
893
+ });
894
+ }
895
+ }
896
+ finally {
897
+ await writer.close();
898
+ }
905
899
  }
906
900
  async runSql(sql) {
907
901
  if (!this.connection) {
908
902
  throw new Error('DuckDB connection is not initialized');
909
903
  }
910
- await new Promise((resolvePromise, rejectPromise) => {
911
- this.connection?.run(sql, (error) => {
912
- if (error) {
913
- rejectPromise(error);
914
- return;
915
- }
916
- resolvePromise();
917
- });
918
- });
904
+ await this.connection.run(sql);
919
905
  }
920
906
  async querySql(sql) {
921
907
  if (!this.connection) {
922
908
  throw new Error('DuckDB connection is not initialized');
923
909
  }
924
- return new Promise((resolvePromise, rejectPromise) => {
925
- this.connection?.all(sql, (error, rows) => {
926
- if (error) {
927
- rejectPromise(error);
928
- return;
929
- }
930
- resolvePromise((rows ?? []));
931
- });
932
- });
910
+ const result = await this.connection.runAndReadAll(sql);
911
+ return result.getRowObjectsJson();
933
912
  }
934
913
  async selectRowsForPaths(paths, context, fromMs, toMs) {
935
914
  const rowsByPath = new Map();
@@ -1142,9 +1121,6 @@ class DuckDbParquetStorageService {
1142
1121
  escape(value) {
1143
1122
  return `'${String(value).replace(/'/g, "''")}'`;
1144
1123
  }
1145
- escapePath(value) {
1146
- return String(value).replace(/'/g, "''");
1147
- }
1148
1124
  nullableString(value) {
1149
1125
  if (value === undefined || value === null || value === '') {
1150
1126
  return 'NULL';