signalk-questdb 2.4.0 → 2.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.
- package/.coderabbit.yaml +17 -2
- package/.release-please-manifest.json +1 -1
- package/AGENTS.md +80 -0
- package/CLAUDE.md +1 -79
- package/README.md +34 -12
- package/dist/api-contract.d.ts +1 -1
- package/dist/change-gate.d.ts +6 -6
- package/dist/change-gate.js +8 -6
- package/dist/change-gate.js.map +1 -1
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.js +6 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +42 -54
- package/dist/index.js.map +1 -1
- package/dist/path-matcher.d.ts +5 -4
- package/dist/path-matcher.js +17 -10
- package/dist/path-matcher.js.map +1 -1
- package/dist/restore.d.ts +10 -6
- package/dist/restore.js +22 -8
- package/dist/restore.js.map +1 -1
- package/package.json +4 -4
- package/public/assets/{AppPanel-C293yEHl.js → AppPanel-D_HLFa10.js} +1 -1
- package/public/assets/PluginConfigurationPanel-DhWC94G3.js +13 -0
- package/public/assets/{_virtual_mf-localSharedImportMap___mfe_internal__signalk_questdb__mf_owner__1-BH9mY6vj.js → _virtual_mf-localSharedImportMap___mfe_internal__signalk_questdb__mf_owner__1-CTTydXPl.js} +1 -1
- package/public/assets/{_virtual_mf___mfe_internal__signalk_questdb__mf_owner__97224418320974__loadShare__shared-H1lSL0nN.js → _virtual_mf___mfe_internal__signalk_questdb__mf_owner__97224418320974__loadShare__shared-BgRwx7LZ.js} +1 -1
- package/public/assets/{dist-BhVCbinB.js → dist-B4My1YVh.js} +1 -1
- package/public/assets/helpers-PTLizuYm.js +1 -0
- package/public/assets/hostInit-Dvn0aCSd.js +1 -0
- package/public/assets/{index-QJC86Uvi.js → index-j5iNctbW.js} +2 -2
- package/public/assets/pendingShares-BHVf8sM9.js +2 -0
- package/public/assets/{utils-D62kKhzq.js → utils-BesU7hIa.js} +1 -1
- package/public/assets/{vite-preload-helper-CRzLMJPS.js → vite-preload-helper-DclECHGd.js} +1 -1
- package/public/remoteEntry.js +2 -2
- package/public/assets/PluginConfigurationPanel-7qfy8fIR.js +0 -11
- package/public/assets/helpers-CvnX61Ju.js +0 -1
- package/public/assets/hostInit-Ds8KD_fS.js +0 -1
- package/public/assets/pendingShares-DemvETkM.js +0 -2
package/dist/index.js
CHANGED
|
@@ -255,13 +255,6 @@ export default (app) => {
|
|
|
255
255
|
return;
|
|
256
256
|
app.setPluginStatus(recordingStatus(host, port));
|
|
257
257
|
};
|
|
258
|
-
// Contexts seen live while a startup restore is in flight. A stored fix
|
|
259
|
-
// must never overwrite a vessel that has already transmitted since boot —
|
|
260
|
-
// that would move it backwards on the chart. Populated by the recorder
|
|
261
|
-
// subscription and cleared as soon as the restore settles, so this stays a
|
|
262
|
-
// startup-window set rather than an unbounded one.
|
|
263
|
-
const liveContexts = new Set();
|
|
264
|
-
let trackLiveContexts = false;
|
|
265
258
|
const withLifecycleLock = (fn) => {
|
|
266
259
|
const run = lifecycleChain.then(fn, fn);
|
|
267
260
|
// Keep the chain alive regardless of this op's outcome.
|
|
@@ -556,10 +549,10 @@ export default (app) => {
|
|
|
556
549
|
const matches = matcher.matches(path);
|
|
557
550
|
return mode === "exclude" ? !matches : matches;
|
|
558
551
|
}
|
|
559
|
-
function isThrottled(path, context, defaultRate) {
|
|
552
|
+
function isThrottled(path, context, defaultRate, source) {
|
|
560
553
|
// Per-path override wins when one matches; otherwise the default rate.
|
|
561
554
|
const overrideRate = rateMatcher?.rateFor(path) ?? null;
|
|
562
|
-
return throttle.shouldDrop(path, context, overrideRate ?? defaultRate, Date.now());
|
|
555
|
+
return throttle.shouldDrop(path, context, overrideRate ?? defaultRate, Date.now(), source);
|
|
563
556
|
}
|
|
564
557
|
const WAL_NOTIFICATION_PATH = "notifications.signalk-questdb.walSuspended";
|
|
565
558
|
// Dedupe key of the last emitted notification. The monitor reports every
|
|
@@ -1189,21 +1182,22 @@ export default (app) => {
|
|
|
1189
1182
|
app.registerHistoryApiProvider(v2Provider);
|
|
1190
1183
|
const v1Provider = createHistoryProviderV1(queryClient, app.selfContext, (msg) => app.debug(msg), archive);
|
|
1191
1184
|
app.registerHistoryProvider(v1Provider);
|
|
1192
|
-
//
|
|
1193
|
-
//
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1185
|
+
// getBus() carries only the source that Signal K's source priorities
|
|
1186
|
+
// prefer on each path, the value instruments show. recordAllSources
|
|
1187
|
+
// reads the unfiltered bus instead, so a source that loses the ranking
|
|
1188
|
+
// is recorded as well: the raw paddlewheel next to a corrected STW
|
|
1189
|
+
// (issue #235).
|
|
1190
|
+
const bus = config.recordAllSources
|
|
1191
|
+
? app.streambundle.getUnfilteredBus()
|
|
1192
|
+
: app.streambundle.getBus();
|
|
1197
1193
|
const unsub = bus.onValue((delta) => {
|
|
1198
1194
|
if (!writer)
|
|
1199
1195
|
return;
|
|
1200
1196
|
const { path, value, context } = delta;
|
|
1201
1197
|
// Values this plugin replayed at startup come straight back around
|
|
1202
1198
|
// through the streambundle. Recording them would re-stamp historical
|
|
1203
|
-
// fixes with the current receive time
|
|
1204
|
-
// positions for vessels that may be long gone
|
|
1205
|
-
// context live would defeat the guard that stops a restore from
|
|
1206
|
-
// overwriting a vessel that actually transmitted.
|
|
1199
|
+
// fixes with the current receive time, inventing present-tense
|
|
1200
|
+
// positions for vessels that may be long gone.
|
|
1207
1201
|
if (delta.$source === RESTORE_SOURCE)
|
|
1208
1202
|
return;
|
|
1209
1203
|
// The delta's sourceRef, stored as the rows' `source` column so
|
|
@@ -1212,13 +1206,21 @@ export default (app) => {
|
|
|
1212
1206
|
// syntax. Undefined when the delta carries none — the column stays
|
|
1213
1207
|
// null, same as rows written before it existed.
|
|
1214
1208
|
const source = typeof delta.$source === "string" ? delta.$source : undefined;
|
|
1215
|
-
//
|
|
1216
|
-
//
|
|
1217
|
-
//
|
|
1218
|
-
//
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1209
|
+
// Recording every source keeps each one apart. It gets its own
|
|
1210
|
+
// sampling window: the shared one would keep whichever source arrives
|
|
1211
|
+
// first, so the fastest receiver would crowd out the ones recorded to
|
|
1212
|
+
// compare against it. And its nulls forget only its own last values:
|
|
1213
|
+
// a gateway sending "not available" several times a second would
|
|
1214
|
+
// otherwise make every other source's unchanged values due again at
|
|
1215
|
+
// each sampling window instead of each heartbeat.
|
|
1216
|
+
const perSource = config.recordAllSources ? source : undefined;
|
|
1217
|
+
// Meta updates ride the same bus as values, flagged isMeta. They
|
|
1218
|
+
// describe a path (units, description, displayName), they are not a
|
|
1219
|
+
// reading of it. Routed like a value object, their keys were stored
|
|
1220
|
+
// as rows such as `….units` and `….displayName`, again each time a
|
|
1221
|
+
// plugin republished its meta.
|
|
1222
|
+
if (delta.isMeta)
|
|
1223
|
+
return;
|
|
1222
1224
|
// Static vessel identity (issue #91): names arrive as empty-path
|
|
1223
1225
|
// object deltas, which the path guard below would drop. Stored as
|
|
1224
1226
|
// path "name" in the string table; history-v1 replays them in the
|
|
@@ -1257,7 +1259,7 @@ export default (app) => {
|
|
|
1257
1259
|
// the change gate must forget the path, so the first reading after the
|
|
1258
1260
|
// gap is recorded even when it equals the last one before it.
|
|
1259
1261
|
if (path && value === null) {
|
|
1260
|
-
changeGate?.forget(path, context === app.selfContext ? "self" : context);
|
|
1262
|
+
changeGate?.forget(path, context === app.selfContext ? "self" : context, perSource);
|
|
1261
1263
|
}
|
|
1262
1264
|
if (!path || value === undefined || value === null)
|
|
1263
1265
|
return;
|
|
@@ -1304,13 +1306,14 @@ export default (app) => {
|
|
|
1304
1306
|
return;
|
|
1305
1307
|
// Throttled per path AND context: the sampling rate bounds each
|
|
1306
1308
|
// vessel's stream, not the fleet's (issue #93). The stored context
|
|
1307
|
-
// ("self" vs raw) is the key, matching what the rows carry.
|
|
1308
|
-
//
|
|
1309
|
-
//
|
|
1310
|
-
//
|
|
1311
|
-
//
|
|
1312
|
-
//
|
|
1313
|
-
|
|
1309
|
+
// ("self" vs raw) is the key, matching what the rows carry. Unless
|
|
1310
|
+
// recordAllSources asks for every source, the key deliberately does
|
|
1311
|
+
// NOT include source: the sampling rate bounds the per-path row
|
|
1312
|
+
// volume, and keying per source would multiply it by the number of
|
|
1313
|
+
// receivers. With two sources alternating, each window keeps
|
|
1314
|
+
// whichever arrived first — a source-filtered read then sees roughly
|
|
1315
|
+
// every other sample, which the rate already allows for.
|
|
1316
|
+
if (isThrottled(path, isSelf ? "self" : context, config.defaultSamplingRate ?? 2000, perSource))
|
|
1314
1317
|
return;
|
|
1315
1318
|
}
|
|
1316
1319
|
// Rows are stamped with the server receive time, deliberately NOT the
|
|
@@ -1365,7 +1368,7 @@ export default (app) => {
|
|
|
1365
1368
|
continue;
|
|
1366
1369
|
if (changeGate?.isRepeat(leaf.path, ctx, source, leaf.value, now))
|
|
1367
1370
|
continue;
|
|
1368
|
-
if (isThrottled(leaf.path, ctx, config.defaultSamplingRate ?? 2000))
|
|
1371
|
+
if (isThrottled(leaf.path, ctx, config.defaultSamplingRate ?? 2000, perSource))
|
|
1369
1372
|
continue;
|
|
1370
1373
|
changeGate?.wrote(leaf.path, ctx, source, leaf.value, now);
|
|
1371
1374
|
if (typeof leaf.value === "number") {
|
|
@@ -1547,11 +1550,10 @@ export default (app) => {
|
|
|
1547
1550
|
// reads pluginRunning and everything before this point can still throw
|
|
1548
1551
|
// and abandon the start.
|
|
1549
1552
|
//
|
|
1550
|
-
//
|
|
1551
|
-
//
|
|
1552
|
-
//
|
|
1553
|
-
//
|
|
1554
|
-
// recorder saw and the restore skips those contexts entirely.
|
|
1553
|
+
// Live deltas keep arriving while the (slow, whole-table) restore query
|
|
1554
|
+
// runs, and some arrived before the recorder subscribed. The restore
|
|
1555
|
+
// reads the model and leaves alone whatever it already holds, so neither
|
|
1556
|
+
// is overwritten by an older stored value.
|
|
1555
1557
|
if (config.restoreOnStart) {
|
|
1556
1558
|
const restoreGeneration = lifecycleGeneration;
|
|
1557
1559
|
void restoreFromHistory({
|
|
@@ -1566,7 +1568,7 @@ export default (app) => {
|
|
|
1566
1568
|
},
|
|
1567
1569
|
selfContext: app.selfContext,
|
|
1568
1570
|
debug: (msg) => app.debug(msg),
|
|
1569
|
-
|
|
1571
|
+
getPath: (path) => app.getPath?.(path),
|
|
1570
1572
|
}, {
|
|
1571
1573
|
maxAgeMs: config.restoreMaxAgeMinutes * 60_000,
|
|
1572
1574
|
restoreSelf: config.recordSelf,
|
|
@@ -1606,16 +1608,6 @@ export default (app) => {
|
|
|
1606
1608
|
const { host, port } = questdbEndpoints.ilp;
|
|
1607
1609
|
publishRecordingStatus(host, port);
|
|
1608
1610
|
}
|
|
1609
|
-
})
|
|
1610
|
-
.finally(() => {
|
|
1611
|
-
// Only needed to shield the restore; tracking every context for the
|
|
1612
|
-
// life of the plugin would be an unbounded set. Guarded on the
|
|
1613
|
-
// generation so a slow restore settling after a restart does not
|
|
1614
|
-
// disarm the tracking the NEW start just armed.
|
|
1615
|
-
if (lifecycleGeneration !== restoreGeneration)
|
|
1616
|
-
return;
|
|
1617
|
-
liveContexts.clear();
|
|
1618
|
-
trackLiveContexts = false;
|
|
1619
1611
|
});
|
|
1620
1612
|
}
|
|
1621
1613
|
}
|
|
@@ -1656,10 +1648,6 @@ export default (app) => {
|
|
|
1656
1648
|
startAbort?.abort();
|
|
1657
1649
|
imageCheckAbort?.abort();
|
|
1658
1650
|
pluginRunning = false;
|
|
1659
|
-
// A start aborted before its restore ran would otherwise leave tracking
|
|
1660
|
-
// armed, accumulating contexts for a restore that never happens.
|
|
1661
|
-
trackLiveContexts = false;
|
|
1662
|
-
liveContexts.clear();
|
|
1663
1651
|
// Stale across a restart otherwise: the card would still claim a
|
|
1664
1652
|
// restore count from the previous run.
|
|
1665
1653
|
restoreSummary = null;
|