@mxtommy/kip 4.5.1 → 4.6.0-beta.2
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/CHANGELOG.md +4 -0
- package/package.json +13 -15
- package/plugin/history-series.service.js +14 -24
- package/plugin/index.js +198 -146
- package/plugin/{duckdb-parquet-storage.service.js → sqlite-history-storage.service.js} +327 -381
- package/public/{chunk-D7VDX7ZF.js → chunk-67V4XHCY.js} +1 -1
- package/public/{chunk-EQ2N7KDA.js → chunk-BEQKBGLG.js} +2 -2
- package/public/{chunk-JGGMFMY5.js → chunk-BJEHRCYP.js} +1 -1
- package/public/chunk-BTFZS2TW.js +16 -0
- package/public/chunk-FZFDGAQO.js +1 -0
- package/public/{chunk-VCY32MWT.js → chunk-FZSLNGBK.js} +8 -8
- package/public/{chunk-RONXIZ2U.js → chunk-J6EEFXKZ.js} +3 -3
- package/public/{chunk-IYRLINL7.js → chunk-KWTS7JF7.js} +1 -1
- package/public/chunk-NFJ4RQSE.js +4 -0
- package/public/{chunk-DEM56G4S.js → chunk-OPTBDYBL.js} +1 -1
- package/public/{chunk-YCEXTKGG.js → chunk-P4CRTB7N.js} +1 -1
- package/public/{chunk-IHURI4IH.js → chunk-P7JKENHI.js} +3 -3
- package/public/chunk-Q2ANAJAD.js +1 -0
- package/public/{chunk-B75MT7ND.js → chunk-R36UY4Q4.js} +1 -1
- package/public/{chunk-CHGXAEKT.js → chunk-RCYOZLZB.js} +1 -1
- package/public/chunk-RFNZ4AQG.js +50 -0
- package/public/{chunk-KPHICV76.js → chunk-SJFJEOSG.js} +1 -1
- package/public/{chunk-MGPPVLZ7.js → chunk-TBNKOU7M.js} +1 -1
- package/public/chunk-TVNXBPFF.js +6 -0
- package/public/{chunk-S72JTJPN.js → chunk-VPF5756E.js} +1 -1
- package/public/chunk-VXCYPAWR.js +1 -0
- package/public/{chunk-R7RQHWKJ.js → chunk-WH5CIUSB.js} +1 -1
- package/public/{chunk-LQDSU4WS.js → chunk-WQSJFJLW.js} +1 -1
- package/public/{chunk-KZ5DUKAX.js → chunk-XBSU7OGT.js} +1 -1
- package/public/{chunk-CEB42O2C.js → chunk-YI3MZWRZ.js} +1 -1
- package/public/index.html +1 -1
- package/public/main-TZOV3JCT.js +1 -0
- package/plugin/plugin-auth.service.js +0 -75
- package/public/chunk-A6DQJFP4.js +0 -16
- package/public/chunk-DEGYRCMI.js +0 -1
- package/public/chunk-DYTBBUMI.js +0 -4
- package/public/chunk-FNF7M3AE.js +0 -1
- package/public/chunk-JB4YVVNW.js +0 -1
- package/public/chunk-YKJKIWXO.js +0 -6
- package/public/chunk-ZV7IYYEQ.js +0 -50
- package/public/main-FQESQQV6.js +0 -1
package/plugin/index.js
CHANGED
|
@@ -34,12 +34,10 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
const server_api_1 = require("@signalk/server-api");
|
|
37
|
-
const module_1 = require("module");
|
|
38
37
|
const openapi = __importStar(require("./openApi.json"));
|
|
39
38
|
const history_series_service_1 = require("./history-series.service");
|
|
40
|
-
const
|
|
39
|
+
const sqlite_history_storage_service_1 = require("./sqlite-history-storage.service");
|
|
41
40
|
const start = (server) => {
|
|
42
|
-
const packageRequire = (0, module_1.createRequire)(__filename);
|
|
43
41
|
const mutableOpenApi = JSON.parse(JSON.stringify(openapi.default ?? openapi));
|
|
44
42
|
const API_PATHS = {
|
|
45
43
|
DISPLAYS: `/displays`,
|
|
@@ -61,6 +59,12 @@ const start = (server) => {
|
|
|
61
59
|
title: 'Remote Control and Data Series',
|
|
62
60
|
description: 'NOTE: All plugin settings are also managed from within KIP\'s Display Options panel. Changes made here will be overridden when KIP applies settings from the Display Options.',
|
|
63
61
|
properties: {
|
|
62
|
+
nodeSqliteAvailable: {
|
|
63
|
+
type: 'boolean',
|
|
64
|
+
title: 'node:sqlite Available',
|
|
65
|
+
description: 'Indicates if node:sqlite is available in the current runtime (requires Node.js version 22.5.0 or newer). This is set automatically and is read-only.\n\nBefore upgrading Node.js, always verify compatibility with your Signal K server version at https://demo.signalk.org/documentation.',
|
|
66
|
+
readOnly: true
|
|
67
|
+
},
|
|
64
68
|
historySeriesServiceEnabled: {
|
|
65
69
|
type: 'boolean',
|
|
66
70
|
title: 'Enable Automatic Historical Time-Series Capture and Management',
|
|
@@ -70,38 +74,59 @@ const start = (server) => {
|
|
|
70
74
|
registerAsHistoryApiProvider: {
|
|
71
75
|
type: 'boolean',
|
|
72
76
|
title: 'Enable Query Provider',
|
|
73
|
-
description: 'The built-in History-API provider is
|
|
77
|
+
description: 'The built-in History-API query provider is a feature that enables the plugin to respond to History-API requests. If you want to use another History-API provider, disable this option and configure your chosen History-API compatible provider accordingly and KIP will query that provider.',
|
|
74
78
|
default: true
|
|
75
79
|
}
|
|
76
80
|
}
|
|
77
81
|
};
|
|
78
|
-
const historySeries = new history_series_service_1.HistorySeriesService(() => Date.now()
|
|
79
|
-
const storageService = new
|
|
82
|
+
const historySeries = new history_series_service_1.HistorySeriesService(() => Date.now());
|
|
83
|
+
const storageService = new sqlite_history_storage_service_1.SqliteHistoryStorageService();
|
|
80
84
|
let retentionSweepTimer = null;
|
|
81
85
|
let storageFlushTimer = null;
|
|
82
|
-
let
|
|
83
|
-
const
|
|
86
|
+
let sqliteInitializationPromise = null;
|
|
87
|
+
const SQLITE_INIT_WAIT_TIMEOUT_MS = 5000;
|
|
88
|
+
const MIN_NODE_SQLITE_VERSION = '22.5.0';
|
|
84
89
|
let streamUnsubscribes = [];
|
|
85
90
|
let historyApiRegistry = null;
|
|
86
|
-
let historySeriesServiceEnabled = true;
|
|
87
|
-
let registerAsHistoryApiProvider = true;
|
|
88
91
|
let historyApiProviderRegistered = false;
|
|
89
|
-
|
|
92
|
+
let runtimeSqliteUnavailableMessage = null;
|
|
93
|
+
function logRuntimeDependencyVersions() {
|
|
94
|
+
const nodeIdentity = `node@${process.version}`;
|
|
95
|
+
const sqliteAvailability = modeConfig && modeConfig.nodeSqliteAvailable ? 'available' : 'unavailable';
|
|
96
|
+
server.debug(`[KIP][RUNTIME] ${nodeIdentity} node:sqlite=${sqliteAvailability}`);
|
|
97
|
+
}
|
|
98
|
+
async function getSqliteModule() {
|
|
90
99
|
try {
|
|
91
|
-
|
|
92
|
-
const name = typeof pkg.name === 'string' && pkg.name.trim() ? pkg.name.trim() : dependencyName;
|
|
93
|
-
const version = typeof pkg.version === 'string' && pkg.version.trim() ? pkg.version.trim() : 'unknown';
|
|
94
|
-
return `${name}@${version}`;
|
|
100
|
+
return await Promise.resolve().then(() => __importStar(require('node:sqlite')));
|
|
95
101
|
}
|
|
96
102
|
catch {
|
|
97
|
-
return
|
|
103
|
+
return null;
|
|
98
104
|
}
|
|
99
105
|
}
|
|
100
|
-
function
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
106
|
+
async function detectSqliteRuntime() {
|
|
107
|
+
const sqliteModule = await getSqliteModule();
|
|
108
|
+
if (!sqliteModule) {
|
|
109
|
+
runtimeSqliteUnavailableMessage = `node:sqlite requires Node ${MIN_NODE_SQLITE_VERSION}+`;
|
|
110
|
+
return false;
|
|
111
|
+
}
|
|
112
|
+
if (!sqliteModule.DatabaseSync && !sqliteModule.Database) {
|
|
113
|
+
runtimeSqliteUnavailableMessage = 'node:sqlite module missing required exports';
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
runtimeSqliteUnavailableMessage = null;
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
function getSqliteUnavailableMessage() {
|
|
120
|
+
if (!(modeConfig && modeConfig.nodeSqliteAvailable)) {
|
|
121
|
+
return `node:sqlite is not supported in the installed Node.js runtime. Node ${MIN_NODE_SQLITE_VERSION}+ is required.`;
|
|
122
|
+
}
|
|
123
|
+
const details = storageService.getLastInitError();
|
|
124
|
+
return details
|
|
125
|
+
? `node:sqlite storage unavailable: ${details}`
|
|
126
|
+
: 'node:sqlite storage unavailable';
|
|
127
|
+
}
|
|
128
|
+
function isSqliteUnavailable() {
|
|
129
|
+
return !(modeConfig && modeConfig.nodeSqliteAvailable) || Boolean(storageService.getLastInitError());
|
|
105
130
|
}
|
|
106
131
|
function resolveHistoryModeConfig(settings) {
|
|
107
132
|
const root = (settings && typeof settings === 'object' ? settings : {});
|
|
@@ -111,12 +136,15 @@ const start = (server) => {
|
|
|
111
136
|
const registerAsHistoryApiProviderSetting = typeof root.registerAsHistoryApiProvider === 'boolean'
|
|
112
137
|
? root.registerAsHistoryApiProvider
|
|
113
138
|
: undefined;
|
|
139
|
+
const nodeSqliteAvailable = typeof root.nodeSqliteAvailable === 'boolean'
|
|
140
|
+
? root.nodeSqliteAvailable
|
|
141
|
+
: undefined;
|
|
114
142
|
return {
|
|
115
143
|
historySeriesServiceEnabled: historySeriesServiceEnabledSetting !== false,
|
|
116
|
-
registerAsHistoryApiProvider: registerAsHistoryApiProviderSetting !== false
|
|
144
|
+
registerAsHistoryApiProvider: registerAsHistoryApiProviderSetting !== false,
|
|
145
|
+
nodeSqliteAvailable: nodeSqliteAvailable !== false
|
|
117
146
|
};
|
|
118
147
|
}
|
|
119
|
-
// Helpers
|
|
120
148
|
function getDisplaySelfPath(displayId, suffix) {
|
|
121
149
|
const tail = suffix ? `.${suffix}` : '';
|
|
122
150
|
const want = `displays.${displayId}${tail}`;
|
|
@@ -140,28 +168,19 @@ const start = (server) => {
|
|
|
140
168
|
function getHistorySeriesServiceDisabledMessage() {
|
|
141
169
|
return 'KIP history-series service is disabled by plugin configuration';
|
|
142
170
|
}
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
? `DuckDB storage unavailable: ${details}`
|
|
147
|
-
: 'DuckDB storage unavailable';
|
|
148
|
-
}
|
|
149
|
-
function isDuckDbUnavailable() {
|
|
150
|
-
return Boolean(storageService.getLastInitError());
|
|
151
|
-
}
|
|
152
|
-
async function waitForDuckDbInitialization(timeoutMs = DUCKDB_INIT_WAIT_TIMEOUT_MS) {
|
|
153
|
-
if (!duckDbInitializationPromise) {
|
|
154
|
-
return storageService.isDuckDbParquetReady();
|
|
171
|
+
async function waitForSqliteInitialization(timeoutMs = SQLITE_INIT_WAIT_TIMEOUT_MS) {
|
|
172
|
+
if (!sqliteInitializationPromise) {
|
|
173
|
+
return storageService.isSqliteReady();
|
|
155
174
|
}
|
|
156
175
|
try {
|
|
157
176
|
const ready = await Promise.race([
|
|
158
|
-
|
|
177
|
+
sqliteInitializationPromise,
|
|
159
178
|
new Promise(resolvePromise => {
|
|
160
179
|
setTimeout(() => resolvePromise(false), timeoutMs);
|
|
161
180
|
})
|
|
162
181
|
]);
|
|
163
|
-
if (!ready && !storageService.
|
|
164
|
-
server.error(`[SERIES STORAGE]
|
|
182
|
+
if (!ready && !storageService.isSqliteReady()) {
|
|
183
|
+
server.error(`[SERIES STORAGE] node:sqlite initialization wait timed out after ${timeoutMs}ms`);
|
|
165
184
|
}
|
|
166
185
|
return ready;
|
|
167
186
|
}
|
|
@@ -179,10 +198,10 @@ const start = (server) => {
|
|
|
179
198
|
|| normalized.includes('expected an iso')) {
|
|
180
199
|
return { statusCode: 400, message };
|
|
181
200
|
}
|
|
182
|
-
if (normalized.includes('
|
|
201
|
+
if (normalized.includes('sqlite')
|
|
183
202
|
|| normalized.includes('storage unavailable')
|
|
184
203
|
|| normalized.includes('not initialized')
|
|
185
|
-
||
|
|
204
|
+
|| isSqliteUnavailable()) {
|
|
186
205
|
return { statusCode: 503, message };
|
|
187
206
|
}
|
|
188
207
|
return { statusCode: 500, message };
|
|
@@ -192,23 +211,27 @@ const start = (server) => {
|
|
|
192
211
|
return current ? JSON.parse(JSON.stringify(current)) : null;
|
|
193
212
|
}
|
|
194
213
|
function isHistorySeriesServiceEnabled() {
|
|
195
|
-
return historySeriesServiceEnabled;
|
|
214
|
+
return !!(modeConfig && modeConfig.historySeriesServiceEnabled && modeConfig.nodeSqliteAvailable);
|
|
196
215
|
}
|
|
197
216
|
function isHistoryApiProviderEnabled() {
|
|
198
|
-
return registerAsHistoryApiProvider;
|
|
217
|
+
return !!(modeConfig && modeConfig.registerAsHistoryApiProvider && modeConfig.nodeSqliteAvailable);
|
|
199
218
|
}
|
|
200
219
|
function logOperationalMode(stage) {
|
|
201
220
|
server.debug(`[HISTORY MODE] stage=${stage} historySeriesServiceEnabled=${isHistorySeriesServiceEnabled()} historyApiProviderEnabled=${isHistoryApiProviderEnabled()} historyApiProviderRegistered=${historyApiProviderRegistered}`);
|
|
202
221
|
}
|
|
203
|
-
async function
|
|
204
|
-
await
|
|
205
|
-
if (storageService.
|
|
222
|
+
async function ensureSqliteReadyForRequest(res) {
|
|
223
|
+
await waitForSqliteInitialization();
|
|
224
|
+
if (storageService.isSqliteReady()) {
|
|
206
225
|
return true;
|
|
207
226
|
}
|
|
208
|
-
sendFail(res, 503,
|
|
227
|
+
sendFail(res, 503, getSqliteUnavailableMessage());
|
|
209
228
|
return false;
|
|
210
229
|
}
|
|
211
230
|
function ensureHistorySeriesServiceEnabledForRequest(res) {
|
|
231
|
+
if (!(modeConfig && modeConfig.nodeSqliteAvailable)) {
|
|
232
|
+
sendFail(res, 503, getSqliteUnavailableMessage());
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
212
235
|
if (isHistorySeriesServiceEnabled()) {
|
|
213
236
|
return true;
|
|
214
237
|
}
|
|
@@ -393,9 +416,9 @@ const start = (server) => {
|
|
|
393
416
|
};
|
|
394
417
|
}
|
|
395
418
|
async function resolveHistoryPaths(query) {
|
|
396
|
-
await
|
|
397
|
-
if (!storageService.
|
|
398
|
-
throw new Error(
|
|
419
|
+
await waitForSqliteInitialization();
|
|
420
|
+
if (!storageService.isSqliteReady()) {
|
|
421
|
+
throw new Error(getSqliteUnavailableMessage());
|
|
399
422
|
}
|
|
400
423
|
try {
|
|
401
424
|
await storageService.flush();
|
|
@@ -408,9 +431,9 @@ const start = (server) => {
|
|
|
408
431
|
});
|
|
409
432
|
}
|
|
410
433
|
async function resolveHistoryContexts(query) {
|
|
411
|
-
await
|
|
412
|
-
if (!storageService.
|
|
413
|
-
throw new Error(
|
|
434
|
+
await waitForSqliteInitialization();
|
|
435
|
+
if (!storageService.isSqliteReady()) {
|
|
436
|
+
throw new Error(getSqliteUnavailableMessage());
|
|
414
437
|
}
|
|
415
438
|
try {
|
|
416
439
|
await storageService.flush();
|
|
@@ -423,9 +446,9 @@ const start = (server) => {
|
|
|
423
446
|
});
|
|
424
447
|
}
|
|
425
448
|
async function resolveHistoryValues(query) {
|
|
426
|
-
await
|
|
427
|
-
if (!storageService.
|
|
428
|
-
throw new Error(
|
|
449
|
+
await waitForSqliteInitialization();
|
|
450
|
+
if (!storageService.isSqliteReady()) {
|
|
451
|
+
throw new Error(getSqliteUnavailableMessage());
|
|
429
452
|
}
|
|
430
453
|
try {
|
|
431
454
|
await storageService.flush();
|
|
@@ -437,7 +460,7 @@ const start = (server) => {
|
|
|
437
460
|
...query
|
|
438
461
|
});
|
|
439
462
|
if (!values) {
|
|
440
|
-
throw new Error('
|
|
463
|
+
throw new Error('node:sqlite storage did not return history values.');
|
|
441
464
|
}
|
|
442
465
|
return values;
|
|
443
466
|
}
|
|
@@ -556,114 +579,128 @@ const start = (server) => {
|
|
|
556
579
|
clearInterval(storageFlushTimer);
|
|
557
580
|
storageFlushTimer = null;
|
|
558
581
|
}
|
|
559
|
-
if (!storageService.
|
|
582
|
+
if (!storageService.isSqliteEnabled()) {
|
|
560
583
|
return;
|
|
561
584
|
}
|
|
562
585
|
storageFlushTimer = setInterval(() => {
|
|
563
586
|
void storageService.flush()
|
|
564
|
-
.then(result => {
|
|
565
|
-
if (result.inserted > 0 || result.exported > 0) {
|
|
566
|
-
server.debug(`[KIP][STORAGE] flush inserted=${result.inserted} exported=${result.exported}`);
|
|
567
|
-
}
|
|
568
|
-
})
|
|
569
587
|
.catch(error => {
|
|
570
588
|
server.error(`[SERIES STORAGE] flush failed: ${String(error.message || error)}`);
|
|
571
589
|
});
|
|
572
590
|
}, intervalMs);
|
|
573
591
|
storageFlushTimer.unref?.();
|
|
574
592
|
}
|
|
593
|
+
let modeConfig = null;
|
|
575
594
|
const plugin = {
|
|
576
595
|
id: 'kip',
|
|
577
596
|
name: 'KIP',
|
|
578
597
|
description: 'KIP server plugin',
|
|
579
|
-
start: (settings) => {
|
|
598
|
+
start: async (settings) => {
|
|
580
599
|
server.debug('[KIP][LIFECYCLE] start');
|
|
600
|
+
modeConfig = resolveHistoryModeConfig(settings);
|
|
601
|
+
// Overwrite runtime-detected properties in modeConfig
|
|
602
|
+
modeConfig.nodeSqliteAvailable = await detectSqliteRuntime();
|
|
603
|
+
if (!modeConfig.nodeSqliteAvailable) {
|
|
604
|
+
server.error(`[KIP][RUNTIME] node:sqlite unavailable. ${runtimeSqliteUnavailableMessage}`);
|
|
605
|
+
}
|
|
606
|
+
const serverWithApp = server;
|
|
607
|
+
const dataDirPath = serverWithApp.app?.getDataDirPath?.();
|
|
608
|
+
storageService.setDataDirPath(typeof dataDirPath === 'string' ? dataDirPath : null);
|
|
609
|
+
storageService.setRuntimeAvailability(modeConfig.nodeSqliteAvailable, runtimeSqliteUnavailableMessage ?? undefined);
|
|
581
610
|
logRuntimeDependencyVersions();
|
|
582
|
-
const modeConfig = resolveHistoryModeConfig(settings);
|
|
583
|
-
historySeriesServiceEnabled = modeConfig.historySeriesServiceEnabled;
|
|
584
|
-
registerAsHistoryApiProvider = modeConfig.registerAsHistoryApiProvider;
|
|
585
611
|
logOperationalMode('start-configured');
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
+
const needsSqlite = (modeConfig.historySeriesServiceEnabled || modeConfig.registerAsHistoryApiProvider) && modeConfig.nodeSqliteAvailable;
|
|
613
|
+
if (needsSqlite) {
|
|
614
|
+
storageService.setLogger({
|
|
615
|
+
debug: (msg) => server.debug(msg),
|
|
616
|
+
error: (msg) => server.error(msg)
|
|
617
|
+
});
|
|
618
|
+
const storageConfig = storageService.configure();
|
|
619
|
+
server.debug(`[KIP][STORAGE] config engine=${storageConfig.engine} db=${storageConfig.databaseFile} flushMs=${storageConfig.flushIntervalMs}`);
|
|
620
|
+
historySeries.setSampleSink(sample => {
|
|
621
|
+
storageService.enqueueSample(sample);
|
|
622
|
+
});
|
|
623
|
+
sqliteInitializationPromise = storageService.initialize();
|
|
624
|
+
void sqliteInitializationPromise.then((ready) => {
|
|
625
|
+
server.debug(`[KIP][STORAGE] sqliteReady=${ready}`);
|
|
626
|
+
if (ready && storageService.isSqliteEnabled()) {
|
|
627
|
+
if (modeConfig && modeConfig.historySeriesServiceEnabled) {
|
|
628
|
+
void storageService.getSeriesDefinitions()
|
|
629
|
+
.then((storedSeries) => {
|
|
630
|
+
if (storedSeries.length > 0) {
|
|
631
|
+
historySeries.reconcileSeries(storedSeries);
|
|
632
|
+
rebuildSeriesCaptureSubscriptions();
|
|
633
|
+
}
|
|
634
|
+
startStorageFlushTimer(storageConfig.flushIntervalMs);
|
|
635
|
+
logOperationalMode('sqlite-ready');
|
|
636
|
+
server.setPluginStatus(`KIP plugin started with node:sqlite history storage. Loaded ${storedSeries.length} persisted series. historySeriesServiceEnabled=${modeConfig.historySeriesServiceEnabled} historyApiProviderEnabled=${modeConfig.registerAsHistoryApiProvider} historyApiProviderRegistered=${historyApiProviderRegistered}`);
|
|
637
|
+
})
|
|
638
|
+
.catch((loadError) => {
|
|
639
|
+
server.error(`[SERIES STORAGE] failed to load persisted series: ${String(loadError.message || loadError)}`);
|
|
640
|
+
startStorageFlushTimer(storageConfig.flushIntervalMs);
|
|
641
|
+
logOperationalMode('sqlite-ready-series-load-failed');
|
|
642
|
+
server.setPluginStatus(`KIP plugin started with node:sqlite history storage. historySeriesServiceEnabled=${modeConfig.historySeriesServiceEnabled} historyApiProviderEnabled=${modeConfig.registerAsHistoryApiProvider} historyApiProviderRegistered=${historyApiProviderRegistered}`);
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
else {
|
|
646
|
+
historySeries.reconcileSeries([]);
|
|
647
|
+
stopSeriesCapture();
|
|
612
648
|
startStorageFlushTimer(storageConfig.flushIntervalMs);
|
|
613
|
-
logOperationalMode('
|
|
614
|
-
server.setPluginStatus(`KIP plugin started with
|
|
615
|
-
}
|
|
649
|
+
logOperationalMode('sqlite-ready-series-disabled');
|
|
650
|
+
server.setPluginStatus(`KIP plugin started with history-series service disabled. historyApiProviderEnabled=${modeConfig.registerAsHistoryApiProvider} historyApiProviderRegistered=${historyApiProviderRegistered}`);
|
|
651
|
+
}
|
|
652
|
+
return;
|
|
616
653
|
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
startStorageFlushTimer(storageConfig.flushIntervalMs);
|
|
621
|
-
logOperationalMode('duckdb-ready-series-disabled');
|
|
622
|
-
server.setPluginStatus(`KIP plugin started with history-series service disabled. historyApiProviderEnabled=${isHistoryApiProviderEnabled()} historyApiProviderRegistered=${historyApiProviderRegistered}`);
|
|
654
|
+
if (storageFlushTimer) {
|
|
655
|
+
clearInterval(storageFlushTimer);
|
|
656
|
+
storageFlushTimer = null;
|
|
623
657
|
}
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
if (
|
|
632
|
-
|
|
633
|
-
logOperationalMode('duckdb-unavailable');
|
|
634
|
-
server.setPluginStatus(`KIP plugin started with DuckDB unavailable. historySeriesServiceEnabled=${isHistorySeriesServiceEnabled()} historyApiProviderEnabled=${isHistoryApiProviderEnabled()} historyApiProviderRegistered=${historyApiProviderRegistered}`);
|
|
658
|
+
const initError = storageService.getLastInitError();
|
|
659
|
+
if (initError) {
|
|
660
|
+
server.setPluginError(`node:sqlite unavailable. ${initError}`);
|
|
661
|
+
logOperationalMode('sqlite-unavailable');
|
|
662
|
+
server.setPluginStatus(`KIP plugin started with node:sqlite unavailable. historySeriesServiceEnabled=${modeConfig?.historySeriesServiceEnabled} historyApiProviderEnabled=${modeConfig?.registerAsHistoryApiProvider} historyApiProviderRegistered=${historyApiProviderRegistered}`);
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
if (retentionSweepTimer) {
|
|
666
|
+
clearInterval(retentionSweepTimer);
|
|
635
667
|
}
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
void storageService.pruneExpiredSamples(Date.now(), lifecycleToken)
|
|
645
|
-
.then(removedPersistedRows => {
|
|
646
|
-
if (removedPersistedRows > 0) {
|
|
647
|
-
server.debug(`[KIP][RETENTION] pruneExpired removedRows=${removedPersistedRows}`);
|
|
648
|
-
}
|
|
649
|
-
return storageService.pruneOrphanedSamples(lifecycleToken)
|
|
650
|
-
.then(removedOrphanRows => {
|
|
651
|
-
if (removedOrphanRows > 0) {
|
|
652
|
-
server.debug(`[KIP][RETENTION] pruneOrphaned removedRows=${removedOrphanRows}`);
|
|
668
|
+
retentionSweepTimer = setInterval(() => {
|
|
669
|
+
try {
|
|
670
|
+
if (storageService.isSqliteReady()) {
|
|
671
|
+
const lifecycleToken = storageService.getLifecycleToken();
|
|
672
|
+
void storageService.pruneExpiredSamples(Date.now(), lifecycleToken)
|
|
673
|
+
.then(removedPersistedRows => {
|
|
674
|
+
if (removedPersistedRows > 0) {
|
|
675
|
+
server.debug(`[KIP][RETENTION] pruneExpired removedRows=${removedPersistedRows}`);
|
|
653
676
|
}
|
|
677
|
+
return storageService.pruneOrphanedSamples(lifecycleToken)
|
|
678
|
+
.then(removedOrphanRows => {
|
|
679
|
+
if (removedOrphanRows > 0) {
|
|
680
|
+
server.debug(`[KIP][RETENTION] pruneOrphaned removedRows=${removedOrphanRows}`);
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
})
|
|
684
|
+
.catch(error => {
|
|
685
|
+
server.error(`[SERIES RETENTION] node:sqlite Prune failed: ${String(error.message || error)}`);
|
|
654
686
|
});
|
|
655
|
-
}
|
|
656
|
-
.catch(error => {
|
|
657
|
-
server.error(`[SERIES RETENTION] duckdbPrune failed: ${String(error.message || error)}`);
|
|
658
|
-
});
|
|
687
|
+
}
|
|
659
688
|
}
|
|
689
|
+
catch (error) {
|
|
690
|
+
server.error(`[SERIES RETENTION] node:sqlite sweep failed: ${String(error.message || error)}`);
|
|
691
|
+
}
|
|
692
|
+
}, 60 * 60_000);
|
|
693
|
+
retentionSweepTimer.unref?.();
|
|
694
|
+
rebuildSeriesCaptureSubscriptions();
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
if (modeConfig && !modeConfig.nodeSqliteAvailable && (modeConfig.historySeriesServiceEnabled || modeConfig.registerAsHistoryApiProvider)) {
|
|
698
|
+
server.setPluginError(getSqliteUnavailableMessage());
|
|
660
699
|
}
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
}
|
|
665
|
-
retentionSweepTimer.unref?.();
|
|
666
|
-
rebuildSeriesCaptureSubscriptions();
|
|
700
|
+
server.debug('[KIP][STORAGE] sqlite init skipped reason=config-disabled-or-runtime');
|
|
701
|
+
sqliteInitializationPromise = null;
|
|
702
|
+
stopSeriesCapture();
|
|
703
|
+
}
|
|
667
704
|
if (server.registerPutHandler) {
|
|
668
705
|
server.debug(`[KIP][COMMAND] registerPutHandlers context=${PUT_CONTEXT}`);
|
|
669
706
|
server.registerPutHandler(PUT_CONTEXT, COMMAND_PATHS.SET_DISPLAY, (context, path, value) => {
|
|
@@ -715,11 +752,26 @@ const start = (server) => {
|
|
|
715
752
|
}
|
|
716
753
|
historyApiRegistry = null;
|
|
717
754
|
}
|
|
718
|
-
|
|
755
|
+
sqliteInitializationPromise = null;
|
|
719
756
|
const msg = 'Stopped.';
|
|
720
757
|
server.setPluginStatus(msg);
|
|
721
758
|
},
|
|
722
|
-
schema: () =>
|
|
759
|
+
schema: () => {
|
|
760
|
+
// Return schema with live modeConfig values
|
|
761
|
+
const schema = JSON.parse(JSON.stringify(CONFIG_SCHEMA));
|
|
762
|
+
if (schema && schema.properties && modeConfig) {
|
|
763
|
+
if (typeof modeConfig.nodeSqliteAvailable === 'boolean') {
|
|
764
|
+
schema.properties.nodeSqliteAvailable.default = modeConfig.nodeSqliteAvailable;
|
|
765
|
+
}
|
|
766
|
+
if (typeof modeConfig.historySeriesServiceEnabled === 'boolean') {
|
|
767
|
+
schema.properties.historySeriesServiceEnabled.default = modeConfig.historySeriesServiceEnabled;
|
|
768
|
+
}
|
|
769
|
+
if (typeof modeConfig.registerAsHistoryApiProvider === 'boolean') {
|
|
770
|
+
schema.properties.registerAsHistoryApiProvider.default = modeConfig.registerAsHistoryApiProvider;
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
return schema;
|
|
774
|
+
},
|
|
723
775
|
registerWithRouter(router) {
|
|
724
776
|
server.debug(`[KIP][ROUTES] register displays=${API_PATHS.DISPLAYS} instance=${API_PATHS.INSTANCE} screenIndex=${API_PATHS.SCREEN_INDEX} activeScreen=${API_PATHS.ACTIVATE_SCREEN}`);
|
|
725
777
|
// Validate/normalize :displayId where present
|
|
@@ -912,7 +964,7 @@ const start = (server) => {
|
|
|
912
964
|
if (!ensureHistorySeriesServiceEnabledForRequest(res)) {
|
|
913
965
|
return;
|
|
914
966
|
}
|
|
915
|
-
if (!(await
|
|
967
|
+
if (!(await ensureSqliteReadyForRequest(res))) {
|
|
916
968
|
return;
|
|
917
969
|
}
|
|
918
970
|
return sendOk(res, historySeries.listSeries());
|
|
@@ -929,7 +981,7 @@ const start = (server) => {
|
|
|
929
981
|
if (!ensureHistorySeriesServiceEnabledForRequest(res)) {
|
|
930
982
|
return;
|
|
931
983
|
}
|
|
932
|
-
if (!(await
|
|
984
|
+
if (!(await ensureSqliteReadyForRequest(res))) {
|
|
933
985
|
return;
|
|
934
986
|
}
|
|
935
987
|
const seriesId = String(req.params.seriesId ?? '').trim();
|
|
@@ -970,7 +1022,7 @@ const start = (server) => {
|
|
|
970
1022
|
if (!ensureHistorySeriesServiceEnabledForRequest(res)) {
|
|
971
1023
|
return;
|
|
972
1024
|
}
|
|
973
|
-
if (!(await
|
|
1025
|
+
if (!(await ensureSqliteReadyForRequest(res))) {
|
|
974
1026
|
return;
|
|
975
1027
|
}
|
|
976
1028
|
const seriesId = String(req.params.seriesId ?? '').trim();
|
|
@@ -998,14 +1050,14 @@ const start = (server) => {
|
|
|
998
1050
|
if (!ensureHistorySeriesServiceEnabledForRequest(res)) {
|
|
999
1051
|
return;
|
|
1000
1052
|
}
|
|
1001
|
-
if (!(await
|
|
1053
|
+
if (!(await ensureSqliteReadyForRequest(res))) {
|
|
1002
1054
|
return;
|
|
1003
1055
|
}
|
|
1004
1056
|
const payload = req.body;
|
|
1005
1057
|
if (!Array.isArray(payload)) {
|
|
1006
1058
|
return sendFail(res, 400, 'Body must be an array of series definitions');
|
|
1007
1059
|
}
|
|
1008
|
-
const simulated = new history_series_service_1.HistorySeriesService(() => Date.now()
|
|
1060
|
+
const simulated = new history_series_service_1.HistorySeriesService(() => Date.now());
|
|
1009
1061
|
historySeries.listSeries().forEach(series => {
|
|
1010
1062
|
simulated.upsertSeries(series);
|
|
1011
1063
|
});
|