@mxtommy/kip 4.7.0-beta.7 → 4.7.0-beta.9

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mxtommy/kip",
3
- "version": "4.7.0-beta.7",
3
+ "version": "4.7.0-beta.9",
4
4
  "description": "An advanced and versatile marine instrumentation package to display Signal K data.",
5
5
  "license": "MIT",
6
6
  "author": {
@@ -1,6 +1,10 @@
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
  */
@@ -381,7 +385,15 @@ class HistorySeriesService {
381
385
  if (!path) {
382
386
  throw new Error('path is required');
383
387
  }
384
- const ownerWidgetSelector = typeof input.ownerWidgetSelector === 'string' ? input.ownerWidgetSelector.trim() : undefined;
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;
385
397
  const isDataWidget = this.isChartWidget(ownerWidgetSelector, ownerWidgetUuid);
386
398
  const retentionMs = this.resolveRetentionMs(input);
387
399
  let sampleTime;
@@ -395,8 +407,7 @@ class HistorySeriesService {
395
407
  // a good median amount of samples for the dynamically queryable chart display range (15 min up to 24h).
396
408
  sampleTime = 15000; // ms
397
409
  }
398
- return {
399
- ...input,
410
+ const normalizedBase = {
400
411
  seriesId,
401
412
  datasetUuid,
402
413
  ownerWidgetUuid,
@@ -404,10 +415,29 @@ class HistorySeriesService {
404
415
  path,
405
416
  source: input.source ?? 'default',
406
417
  context: input.context ?? 'vessels.self',
418
+ timeScale: input.timeScale ?? null,
419
+ period: Number.isFinite(input.period) ? input.period ?? null : null,
407
420
  enabled: input.enabled !== false,
408
421
  retentionDurationMs: retentionMs,
409
- sampleTime
422
+ sampleTime,
423
+ methods: normalizedMethods,
424
+ reconcileTs: input.reconcileTs
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
410
439
  };
440
+ return concreteSeries;
411
441
  }
412
442
  resolveRetentionMs(series) {
413
443
  if (Number.isFinite(series.retentionDurationMs) && series.retentionDurationMs > 0) {
package/plugin/index.js CHANGED
@@ -88,9 +88,6 @@ const start = (server) => {
88
88
  }
89
89
  };
90
90
  const historySeries = new history_series_service_1.HistorySeriesService(() => Date.now(), typeof server.selfId === 'string' && server.selfId.trim().length > 0 ? `vessels.${server.selfId.trim()}` : null);
91
- if (typeof server.getDataDirPath !== 'function') {
92
- throw new Error('Signal K Server API does not expose getDataDirPath()');
93
- }
94
91
  const storageService = new sqlite_history_storage_service_1.SqliteHistoryStorageService(server.getDataDirPath());
95
92
  let retentionSweepTimer = null;
96
93
  let storageFlushTimer = null;
@@ -184,7 +181,7 @@ const start = (server) => {
184
181
  function getExistingConcreteBmsSeries(templateSeries, existingSeries) {
185
182
  return existingSeries
186
183
  .filter(series => series.ownerWidgetUuid === templateSeries.ownerWidgetUuid)
187
- .filter(series => series.expansionMode == null)
184
+ .filter(history_series_service_1.isKipConcreteSeriesDefinition)
188
185
  .filter(series => series.seriesId !== templateSeries.seriesId)
189
186
  .map(series => ({ ...series }));
190
187
  }
@@ -200,8 +197,7 @@ const start = (server) => {
200
197
  const expandedById = new Map();
201
198
  const discoveredBatteryIds = resolveBmsBatteryIdsFromSelfPath();
202
199
  payload.forEach(series => {
203
- const isBmsTemplate = series.expansionMode === 'bms-battery-tree' && series.ownerWidgetSelector === 'widget-bms';
204
- if (!isBmsTemplate) {
200
+ if (!(0, history_series_service_1.isKipTemplateSeriesDefinition)(series)) {
205
201
  expandedById.set(series.seriesId, series);
206
202
  return;
207
203
  }
@@ -234,8 +230,7 @@ const start = (server) => {
234
230
  datasetUuid: `${series.ownerWidgetUuid}:bms:${batteryId}:${metric}:${sourceKey}`,
235
231
  path,
236
232
  retentionDurationMs: Number.isFinite(series.retentionDurationMs) ? series.retentionDurationMs : 24 * 60 * 60 * 1000,
237
- expansionMode: null,
238
- allowedBatteryIds: null
233
+ expansionMode: null
239
234
  });
240
235
  });
241
236
  });
@@ -620,7 +615,7 @@ const start = (server) => {
620
615
  // If any series for this path requires non-self context, force generic bus subscription.
621
616
  existing.allSelfContext = existing.allSelfContext && allSelfContext;
622
617
  };
623
- historySeries.listSeries().filter(series => series.enabled !== false).forEach(series => {
618
+ historySeries.listSeries().filter(history_series_service_1.isKipSeriesEnabled).forEach(series => {
624
619
  const allSelfContext = (series.context ?? 'vessels.self') === 'vessels.self';
625
620
  addCandidate(series.path, allSelfContext);
626
621
  // Workaround: subscribe to immediate parent path so object deltas (e.g. navigation.attitude)
@@ -1158,8 +1153,8 @@ const start = (server) => {
1158
1153
  const isBatteryDiscoveryUnavailable = resolveBmsBatteryIdsFromSelfPath().length === 0;
1159
1154
  const preservedBmsSeries = isBatteryDiscoveryUnavailable
1160
1155
  ? scopedPayload
1161
- .filter(series => series.expansionMode === 'bms-battery-tree' && series.ownerWidgetSelector === 'widget-bms')
1162
- .flatMap(series => currentSeries.filter(current => current.ownerWidgetUuid === series.ownerWidgetUuid && current.expansionMode == null && current.seriesId !== series.seriesId))
1156
+ .filter(history_series_service_1.isKipTemplateSeriesDefinition)
1157
+ .flatMap(series => currentSeries.filter(current => current.ownerWidgetUuid === series.ownerWidgetUuid && (0, history_series_service_1.isKipConcreteSeriesDefinition)(current) && current.seriesId !== series.seriesId))
1163
1158
  : [];
1164
1159
  const expandedPayload = mergeSeriesDefinitions([
1165
1160
  ...expandTemplateSeriesDefinitions(scopedPayload, currentSeries),
@@ -131,7 +131,7 @@
131
131
  "nullable": true,
132
132
  "additionalProperties": true
133
133
  },
134
- "SeriesDefinition": {
134
+ "SeriesDefinitionBase": {
135
135
  "type": "object",
136
136
  "properties": {
137
137
  "seriesId": {
@@ -150,20 +150,6 @@
150
150
  "path": {
151
151
  "type": "string"
152
152
  },
153
- "expansionMode": {
154
- "type": "string",
155
- "nullable": true,
156
- "enum": [
157
- "bms-battery-tree"
158
- ]
159
- },
160
- "allowedBatteryIds": {
161
- "type": "array",
162
- "nullable": true,
163
- "items": {
164
- "type": "string"
165
- }
166
- },
167
153
  "context": {
168
154
  "type": "string",
169
155
  "nullable": true
@@ -191,13 +177,101 @@
191
177
  "enabled": {
192
178
  "type": "boolean",
193
179
  "default": true
180
+ },
181
+ "methods": {
182
+ "type": "array",
183
+ "items": {
184
+ "type": "string",
185
+ "enum": [
186
+ "min",
187
+ "max",
188
+ "avg",
189
+ "sma",
190
+ "ema"
191
+ ]
192
+ }
193
+ },
194
+ "reconcileTs": {
195
+ "type": "integer",
196
+ "nullable": true
194
197
  }
195
198
  },
196
199
  "required": [
197
200
  "seriesId",
198
201
  "datasetUuid",
199
202
  "ownerWidgetUuid",
200
- "path"
203
+ "ownerWidgetSelector",
204
+ "path",
205
+ "enabled"
206
+ ]
207
+ },
208
+ "ConcreteSeriesDefinition": {
209
+ "allOf": [
210
+ {
211
+ "$ref": "#/components/schemas/SeriesDefinitionBase"
212
+ },
213
+ {
214
+ "type": "object",
215
+ "properties": {
216
+ "expansionMode": {
217
+ "nullable": true,
218
+ "description": "Null for concrete series definitions."
219
+ },
220
+ "allowedBatteryIds": {
221
+ "type": "array",
222
+ "nullable": true,
223
+ "items": {
224
+ "type": "string"
225
+ },
226
+ "description": "Concrete series do not use battery filters and should leave this null."
227
+ }
228
+ }
229
+ }
230
+ ]
231
+ },
232
+ "TemplateSeriesDefinition": {
233
+ "allOf": [
234
+ {
235
+ "$ref": "#/components/schemas/SeriesDefinitionBase"
236
+ },
237
+ {
238
+ "type": "object",
239
+ "properties": {
240
+ "ownerWidgetSelector": {
241
+ "type": "string",
242
+ "enum": [
243
+ "widget-bms"
244
+ ]
245
+ },
246
+ "expansionMode": {
247
+ "type": "string",
248
+ "enum": [
249
+ "bms-battery-tree"
250
+ ]
251
+ },
252
+ "allowedBatteryIds": {
253
+ "type": "array",
254
+ "nullable": true,
255
+ "items": {
256
+ "type": "string"
257
+ }
258
+ }
259
+ },
260
+ "required": [
261
+ "ownerWidgetSelector",
262
+ "expansionMode"
263
+ ]
264
+ }
265
+ ]
266
+ },
267
+ "SeriesDefinition": {
268
+ "oneOf": [
269
+ {
270
+ "$ref": "#/components/schemas/ConcreteSeriesDefinition"
271
+ },
272
+ {
273
+ "$ref": "#/components/schemas/TemplateSeriesDefinition"
274
+ }
201
275
  ]
202
276
  },
203
277
  "SeriesReconcileResult": {
@@ -66,7 +66,7 @@ class SqliteHistoryStorageService {
66
66
  pruneJob = null;
67
67
  staleSeriesCleanupJob = null;
68
68
  constructor(dataDirPath) {
69
- const normalizedDataDirPath = typeof dataDirPath === 'string' ? dataDirPath.trim() : '';
69
+ const normalizedDataDirPath = String(dataDirPath);
70
70
  if (!normalizedDataDirPath) {
71
71
  throw new Error('SqliteHistoryStorageService requires a valid dataDirPath from server.getDataDirPath()');
72
72
  }
@@ -310,7 +310,7 @@ class SqliteHistoryStorageService {
310
310
  seriesId: row.series_id,
311
311
  datasetUuid: row.dataset_uuid,
312
312
  ownerWidgetUuid: row.owner_widget_uuid,
313
- ownerWidgetSelector: row.owner_widget_selector ?? undefined,
313
+ ownerWidgetSelector: row.owner_widget_selector ?? null,
314
314
  path: row.path,
315
315
  source: row.source ?? undefined,
316
316
  context: row.context ?? undefined,