@camstack/system 1.2.456 → 1.2.457

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 (117) hide show
  1. package/dist/addon-runner.js +96 -10
  2. package/dist/addon-runner.mjs +96 -10
  3. package/dist/{agent-https-fm4AR8XL.mjs → agent-https-CK9Z4sSR.mjs} +2 -2
  4. package/dist/{agent-https-yjxdfJDA.js → agent-https-CoyOZ3pC.js} +2 -2
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/alerts/alerts.addon.js +2 -2
  10. package/dist/builtins/alerts/alerts.addon.mjs +2 -2
  11. package/dist/builtins/autotrack/index.js +1 -1
  12. package/dist/builtins/autotrack/index.mjs +1 -1
  13. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  14. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  15. package/dist/builtins/calendars/calendars.addon.js +1 -1
  16. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  17. package/dist/builtins/camera-grid/index.js +1 -1
  18. package/dist/builtins/camera-grid/index.mjs +1 -1
  19. package/dist/builtins/composer/composer.addon.js +2 -2
  20. package/dist/builtins/composer/composer.addon.mjs +2 -2
  21. package/dist/builtins/console-logging/console-destination.d.ts +1 -2
  22. package/dist/builtins/console-logging/index.js +1 -4
  23. package/dist/builtins/console-logging/index.mjs +1 -4
  24. package/dist/builtins/core-blocks/core-blocks.addon.js +2 -2
  25. package/dist/builtins/core-blocks/core-blocks.addon.mjs +2 -2
  26. package/dist/builtins/device-history/capture/capture-engine.d.ts +6 -0
  27. package/dist/builtins/device-history/device-history.addon.js +441 -83
  28. package/dist/builtins/device-history/device-history.addon.mjs +441 -83
  29. package/dist/builtins/device-history/query/history-query.d.ts +35 -9
  30. package/dist/builtins/device-history/query/point-shape.d.ts +33 -0
  31. package/dist/builtins/device-history/retention/retention.d.ts +1 -1
  32. package/dist/builtins/device-history/rollup/rollup-engine.d.ts +2 -0
  33. package/dist/builtins/device-history/rollup/rollup-math.d.ts +6 -0
  34. package/dist/builtins/device-history/store/history-store.d.ts +7 -0
  35. package/dist/builtins/device-history/store/series-registry.d.ts +26 -1
  36. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  37. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  38. package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +1 -2
  39. package/dist/builtins/hub-forwarder/index.js +1 -4
  40. package/dist/builtins/hub-forwarder/index.mjs +1 -4
  41. package/dist/builtins/keychain/index.js +4 -4
  42. package/dist/builtins/keychain/index.mjs +4 -4
  43. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  44. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  45. package/dist/builtins/local-auth/local-auth.addon.js +3 -3
  46. package/dist/builtins/local-auth/local-auth.addon.mjs +3 -3
  47. package/dist/builtins/local-network/local-network.addon.js +2 -2
  48. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  49. package/dist/builtins/loki-logging/index.js +323 -7
  50. package/dist/builtins/loki-logging/index.mjs +323 -7
  51. package/dist/builtins/loki-logging/loki-destination.d.ts +20 -3
  52. package/dist/builtins/loki-logging/loki-read.d.ts +51 -0
  53. package/dist/builtins/native-metrics/native-metrics.addon.js +290 -112
  54. package/dist/builtins/native-metrics/native-metrics.addon.mjs +290 -112
  55. package/dist/builtins/native-metrics/node-metrics-history-recorder.d.ts +106 -35
  56. package/dist/builtins/platform-probe/index.js +1 -1
  57. package/dist/builtins/platform-probe/index.mjs +1 -1
  58. package/dist/builtins/record-entries/index.js +1 -1
  59. package/dist/builtins/record-entries/index.mjs +1 -1
  60. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  61. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  62. package/dist/builtins/snapshot/index.js +1 -1
  63. package/dist/builtins/snapshot/index.mjs +1 -1
  64. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  65. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  66. package/dist/builtins/sqlite-storage/index.js +1 -1
  67. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  68. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  69. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  70. package/dist/builtins/storage-orchestrator/location-history-recorder.d.ts +76 -0
  71. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +9 -0
  72. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +144 -1
  73. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +144 -1
  74. package/dist/builtins/system-config/system-config.addon.js +3 -3
  75. package/dist/builtins/system-config/system-config.addon.mjs +3 -3
  76. package/dist/builtins/winston-logging/index.js +165 -3
  77. package/dist/builtins/winston-logging/index.mjs +165 -3
  78. package/dist/builtins/winston-logging/log-file-cursor.d.ts +12 -0
  79. package/dist/builtins/winston-logging/log-file-index-store.d.ts +11 -0
  80. package/dist/builtins/winston-logging/log-file-index.d.ts +77 -0
  81. package/dist/builtins/winston-logging/log-file-read-client.d.ts +44 -0
  82. package/dist/builtins/winston-logging/log-file-read-protocol.d.ts +41 -0
  83. package/dist/builtins/winston-logging/log-file-read-worker.d.ts +1 -0
  84. package/dist/builtins/winston-logging/log-file-reader.d.ts +51 -0
  85. package/dist/builtins/winston-logging/winston-destination.d.ts +20 -2
  86. package/dist/{composition-sources-Ds0Vr6eD.mjs → composition-sources-B_jeZDRJ.mjs} +1 -1
  87. package/dist/{composition-sources-gB4coDJR.js → composition-sources-qKyvMAvc.js} +1 -1
  88. package/dist/{constants-CavyKfHZ.js → constants-B_X9cWEK.js} +1 -1
  89. package/dist/{constants-i5oD_gLB.mjs → constants-cxKcZRMi.mjs} +1 -1
  90. package/dist/{device-ca-qz1Pmeu1.mjs → device-ca-BB6nHAL5.mjs} +1 -1
  91. package/dist/{device-ca-bpoEr0zG.js → device-ca-CVK3YcR_.js} +1 -1
  92. package/dist/{dist-BpqA2CT0.js → dist-C89nqNnn.js} +471 -47
  93. package/dist/{dist-BSF_C8W2.mjs → dist-Dhux90yB.mjs} +454 -48
  94. package/dist/file-host-worker.js +1 -1
  95. package/dist/file-host-worker.mjs +1 -1
  96. package/dist/{hub-mdns-ciao-CHaGDzyz.mjs → hub-mdns-ciao-B8olZqqd.mjs} +2 -2
  97. package/dist/{hub-mdns-ciao-CZlZRxxA.js → hub-mdns-ciao-DB1Y9agt.js} +2 -2
  98. package/dist/index.d.ts +1 -0
  99. package/dist/index.js +135 -20
  100. package/dist/index.mjs +129 -21
  101. package/dist/kernel/config-schema.d.ts +5 -0
  102. package/dist/kernel/moleculer/accessory-prune-gate.d.ts +55 -0
  103. package/dist/log-file-read-protocol-B7M3X3bT.mjs +785 -0
  104. package/dist/log-file-read-protocol-D0WcLbIV.js +817 -0
  105. package/dist/log-file-read-worker.js +64 -0
  106. package/dist/log-file-read-worker.mjs +64 -0
  107. package/dist/log-read-filter-Bi066jwU.js +134 -0
  108. package/dist/log-read-filter-D7Ev3zm-.mjs +93 -0
  109. package/dist/logging/log-manager.d.ts +10 -6
  110. package/dist/logging/log-read-filter.d.ts +43 -0
  111. package/dist/{phase6-declare-gate-n-IZRklX.js → phase6-declare-gate-BO-_jLh_.js} +1 -1
  112. package/dist/{phase6-declare-gate-B4yfsf2n.mjs → phase6-declare-gate-x9ZCbQJP.mjs} +1 -1
  113. package/dist/{restore-archive-B4QG7XmX.js → restore-archive-4DOUR5Gu.js} +1 -1
  114. package/dist/{restore-archive-BGUM4FKf.mjs → restore-archive-DyOaPeAr.mjs} +1 -1
  115. package/dist/{wal-maintenance-AerF4XE6.mjs → wal-maintenance-BMiX6UIa.mjs} +0 -0
  116. package/dist/{wal-maintenance-CNEs_I94.js → wal-maintenance-DVN-DiVP.js} +0 -0
  117. package/package.json +1 -1
@@ -3,7 +3,9 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BpqA2CT0.js");
6
+ const require_dist = require("../../dist-C89nqNnn.js");
7
+ const require_log_read_filter = require("../../log-read-filter-Bi066jwU.js");
8
+ let zod = require("zod");
7
9
  //#region src/builtins/loki-logging/loki-payload.ts
8
10
  /**
9
11
  * Loki rejects a label name that is not a valid Prometheus label
@@ -41,6 +43,7 @@ function toUnixNano(timestamp) {
41
43
  */
42
44
  function formatLokiLine(entry) {
43
45
  const line = { message: entry.message };
46
+ if (entry.id !== void 0) line["id"] = entry.id;
44
47
  if (entry.scope !== void 0 && entry.scope.length > 0) line["scope"] = entry.scope;
45
48
  if (entry.tags !== void 0 && Object.keys(entry.tags).length > 0) line["tags"] = entry.tags;
46
49
  if (entry.meta !== void 0 && Object.keys(entry.meta).length > 0) line["meta"] = entry.meta;
@@ -91,6 +94,231 @@ function buildPushBody(entries, labels) {
91
94
  return { streams };
92
95
  }
93
96
  //#endregion
97
+ //#region src/builtins/loki-logging/loki-read.ts
98
+ /**
99
+ * Reading history back from Loki (D898): `GET /loki/api/v1/query_range`,
100
+ * newest first.
101
+ *
102
+ * The label selector narrows by what IS a label (`job`, `node`, `level` —
103
+ * see `loki-payload.ts` for why nothing else is); line filters (`|=`, `|~`)
104
+ * narrow by device, addon and text on the JSON body; the shared
105
+ * `lineMatches` then decides exactly, so a line filter only has to be a
106
+ * superset.
107
+ *
108
+ * The cursor is the nanosecond timestamp of the oldest line returned plus a
109
+ * short hash of each line returned AT that nanosecond: entries share
110
+ * timestamps (they are millisecond-precise), so "older than t" would drop the
111
+ * siblings a page cut through, and "t or older" would repeat them. The next
112
+ * page asks for `end = t + 1` (Loki's end is exclusive) and skips the hashes.
113
+ */
114
+ var LEVELS = [
115
+ "debug",
116
+ "info",
117
+ "warn",
118
+ "error"
119
+ ];
120
+ /** One query_range window. A busy stream fills `limit` long before this. */
121
+ var LOKI_WINDOW_MS = 360 * 60 * 1e3;
122
+ var MS_NS = 1000000n;
123
+ var CURSOR_PREFIX = "l1.";
124
+ var LokiCursorSchema = zod.z.object({
125
+ /** Nanosecond epoch, decimal string. */
126
+ n: zod.z.string().regex(/^\d{1,22}$/),
127
+ /** Hashes of the lines already returned at exactly `n`. */
128
+ h: zod.z.array(zod.z.string().max(16)).max(1e3)
129
+ });
130
+ function encodeLokiCursor(cursor) {
131
+ return `${CURSOR_PREFIX}${Buffer.from(JSON.stringify(cursor)).toString("base64url")}`;
132
+ }
133
+ function decodeLokiCursor(raw) {
134
+ if (!raw.startsWith(CURSOR_PREFIX)) return null;
135
+ try {
136
+ const parsed = LokiCursorSchema.safeParse(JSON.parse(Buffer.from(raw.slice(3), "base64url").toString("utf8")));
137
+ return parsed.success ? parsed.data : null;
138
+ } catch {
139
+ return null;
140
+ }
141
+ }
142
+ /** FNV-1a, 32 bit, hex — a dedupe key, not a security property. */
143
+ function lineHash(line) {
144
+ let h = 2166136261;
145
+ for (let i = 0; i < line.length; i += 1) {
146
+ h ^= line.charCodeAt(i);
147
+ h = Math.imul(h, 16777619);
148
+ }
149
+ return (h >>> 0).toString(16);
150
+ }
151
+ /** A LogQL double-quoted string literal. */
152
+ function quote(value) {
153
+ return `"${value.replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
154
+ }
155
+ function escapeRegex(value) {
156
+ return value.replaceAll(/[\\.+*?()|[\]{}^$]/g, (c) => `\\${c}`);
157
+ }
158
+ /** The LogQL for a read. Pure. */
159
+ function buildLogQl(query, labels) {
160
+ const selectors = [`job=${quote(labels.job)}`];
161
+ if (query.fromNodeId !== void 0) selectors.push(`node=${quote(query.fromNodeId)}`);
162
+ if (query.level !== void 0 && query.level !== "debug") {
163
+ const floor = LEVELS.indexOf(query.level);
164
+ selectors.push(`level=~${quote(LEVELS.slice(floor).join("|"))}`);
165
+ }
166
+ let logql = `{${selectors.join(",")}}`;
167
+ if (query.deviceId !== void 0) logql += ` |= ${quote(`"deviceId":${String(query.deviceId)}`)}`;
168
+ if (query.fromAddonId !== void 0) {
169
+ const bare = query.fromAddonId.split("@")[0] ?? query.fromAddonId;
170
+ logql += ` |= ${quote(`"addonId":"${bare}`)}`;
171
+ }
172
+ const text = query.text?.trim();
173
+ if (text !== void 0 && text.length > 0) logql += ` |~ ${quote(`(?i)${escapeRegex(text)}`)}`;
174
+ return logql;
175
+ }
176
+ var LokiResponseSchema = zod.z.object({ data: zod.z.object({ result: zod.z.array(zod.z.object({
177
+ stream: zod.z.record(zod.z.string(), zod.z.string()),
178
+ values: zod.z.array(zod.z.tuple([zod.z.string(), zod.z.string()]))
179
+ })) }) });
180
+ var LokiLineSchema = zod.z.object({
181
+ id: zod.z.string().optional(),
182
+ message: zod.z.string(),
183
+ scope: zod.z.string().optional(),
184
+ tags: zod.z.record(zod.z.string(), zod.z.unknown()).optional(),
185
+ meta: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
186
+ });
187
+ function levelOf(label) {
188
+ return label === "debug" || label === "warn" || label === "error" ? label : "info";
189
+ }
190
+ /** A query_range body → rows, newest first. Lines that are not ours are dropped. */
191
+ function parseQueryRange(body) {
192
+ const parsed = LokiResponseSchema.safeParse(body);
193
+ if (!parsed.success) throw new Error("Loki answered with an unexpected query_range shape");
194
+ const rows = [];
195
+ for (const stream of parsed.data.data.result) {
196
+ const level = levelOf(stream.stream["level"]);
197
+ for (const [nsText, text] of stream.values) {
198
+ let json;
199
+ try {
200
+ json = JSON.parse(text);
201
+ } catch {
202
+ continue;
203
+ }
204
+ const line = LokiLineSchema.safeParse(json);
205
+ if (!line.success) continue;
206
+ const ns = BigInt(nsText);
207
+ const timestamp = Number(ns / MS_NS);
208
+ const tags = {};
209
+ for (const [k, v] of Object.entries(line.data.tags ?? {})) if (typeof v === "string" || typeof v === "number") tags[k] = v;
210
+ const hasTags = Object.keys(tags).length > 0;
211
+ const entry = {
212
+ ...line.data.id !== void 0 ? { id: line.data.id } : {},
213
+ timestamp,
214
+ level,
215
+ ...line.data.scope !== void 0 ? { scope: line.data.scope } : {},
216
+ message: line.data.message,
217
+ ...hasTags ? { tags } : {},
218
+ ...line.data.meta !== void 0 ? { meta: line.data.meta } : {}
219
+ };
220
+ rows.push({
221
+ ns,
222
+ hash: lineHash(text),
223
+ entry,
224
+ line: {
225
+ timestamp,
226
+ level,
227
+ message: line.data.message,
228
+ scope: line.data.scope,
229
+ tags: hasTags ? tags : void 0,
230
+ meta: line.data.meta
231
+ }
232
+ });
233
+ }
234
+ }
235
+ return rows.toSorted((a, b) => a.ns < b.ns ? 1 : a.ns > b.ns ? -1 : 0);
236
+ }
237
+ /**
238
+ * One backward slice over Loki: windows of {@link LOKI_WINDOW_MS}, newest
239
+ * first, until `limit` matches, the horizon, the budget or a cancel.
240
+ */
241
+ async function readLokiBackward(args) {
242
+ const { query } = args;
243
+ const now = args.now ?? Date.now;
244
+ const filter = require_log_read_filter.compileLogFilter(query);
245
+ const limit = query.limit ?? 200;
246
+ const deadline = now() + Math.min(query.budgetMs ?? 2e3, 1e4);
247
+ const logql = buildLogQl(query, args.labels);
248
+ const horizonMs = query.since ?? now() - 30 * 864e5;
249
+ const horizonNs = BigInt(Math.trunc(horizonMs)) * MS_NS;
250
+ const cursor = query.before !== void 0 ? decodeLokiCursor(query.before) : null;
251
+ const untilNs = query.until !== void 0 ? BigInt(Math.trunc(query.until) + 1) * MS_NS : null;
252
+ let endNs = cursor !== null ? BigInt(cursor.n) + 1n : untilNs ?? BigInt(now() + 1) * MS_NS;
253
+ let seenAtBoundary = new Set(cursor?.h ?? []);
254
+ let boundaryNs = cursor !== null ? BigInt(cursor.n) : null;
255
+ const entries = [];
256
+ let bytes = 0;
257
+ while (endNs > horizonNs) {
258
+ if (args.isCancelled()) return {
259
+ entries,
260
+ nextBefore: encodeLokiCursor({
261
+ n: String(endNs - 1n),
262
+ h: [...seenAtBoundary]
263
+ }),
264
+ nextAfter: null,
265
+ scannedBytes: bytes,
266
+ done: false,
267
+ cancelled: true
268
+ };
269
+ const windowStart = endNs - BigInt(LOKI_WINDOW_MS) * MS_NS;
270
+ const startNs = windowStart > horizonNs ? windowStart : horizonNs;
271
+ const want = limit - entries.length + seenAtBoundary.size;
272
+ const { rows, bytes: got } = await args.range({
273
+ logql,
274
+ startNs,
275
+ endNs,
276
+ limit: want
277
+ });
278
+ bytes += got;
279
+ let oldest = null;
280
+ for (const row of rows) {
281
+ if (boundaryNs !== null && row.ns === boundaryNs && seenAtBoundary.has(row.hash)) continue;
282
+ if (oldest === null || row.ns < oldest) {
283
+ if (oldest !== null && row.ns < oldest) seenAtBoundary = /* @__PURE__ */ new Set();
284
+ oldest = row.ns;
285
+ }
286
+ if (row.ns === oldest) seenAtBoundary.add(row.hash);
287
+ boundaryNs = oldest;
288
+ if (!require_log_read_filter.lineMatches(row.line, filter)) continue;
289
+ entries.push(row.entry);
290
+ if (entries.length >= limit) return {
291
+ entries,
292
+ nextBefore: encodeLokiCursor({
293
+ n: String(row.ns),
294
+ h: [...seenAtBoundary]
295
+ }),
296
+ nextAfter: null,
297
+ scannedBytes: bytes,
298
+ done: false
299
+ };
300
+ }
301
+ if (rows.length >= want && oldest !== null) endNs = oldest + 1n;
302
+ else {
303
+ endNs = startNs;
304
+ seenAtBoundary = /* @__PURE__ */ new Set();
305
+ boundaryNs = null;
306
+ }
307
+ if (now() >= deadline) break;
308
+ }
309
+ const done = endNs <= horizonNs;
310
+ return {
311
+ entries,
312
+ nextBefore: done ? null : encodeLokiCursor({
313
+ n: String(endNs - 1n),
314
+ h: [...seenAtBoundary]
315
+ }),
316
+ nextAfter: null,
317
+ scannedBytes: bytes,
318
+ done
319
+ };
320
+ }
321
+ //#endregion
94
322
  //#region src/builtins/loki-logging/loki-destination.ts
95
323
  var LEVEL_ORDER = {
96
324
  debug: 0,
@@ -114,6 +342,8 @@ var LokiDestination = class {
114
342
  failures = 0;
115
343
  lastError = null;
116
344
  lastPushAtMs = null;
345
+ /** In-flight history reads, by requestId, so `cancelRead` can abort the fetch. */
346
+ reads = /* @__PURE__ */ new Map();
117
347
  fetchFn;
118
348
  setIntervalFn;
119
349
  clearIntervalFn;
@@ -121,7 +351,7 @@ var LokiDestination = class {
121
351
  this.fetchFn = deps?.fetchFn ?? ((url, init) => globalThis.fetch(url, {
122
352
  method: init.method,
123
353
  headers: { ...init.headers },
124
- body: init.body,
354
+ ...init.body !== void 0 ? { body: init.body } : {},
125
355
  ...init.signal ? { signal: init.signal } : {}
126
356
  }));
127
357
  this.setIntervalFn = deps?.setIntervalFn ?? setInterval;
@@ -213,9 +443,10 @@ var LokiDestination = class {
213
443
  }
214
444
  async push(body, config) {
215
445
  const endpoint = `${config.url.replace(/\/+$/, "")}/loki/api/v1/push`;
216
- const headers = { "content-type": "application/json" };
217
- if (config.tenantId.trim().length > 0) headers["X-Scope-OrgID"] = config.tenantId.trim();
218
- if (config.username.length > 0) headers["authorization"] = `Basic ${Buffer.from(`${config.username}:${config.password}`).toString("base64")}`;
446
+ const headers = {
447
+ "content-type": "application/json",
448
+ ...this.authHeaders(config)
449
+ };
219
450
  const controller = new AbortController();
220
451
  const timeout = setTimeout(() => controller.abort(), Math.max(1e3, config.timeoutMs));
221
452
  try {
@@ -283,8 +514,93 @@ var LokiDestination = class {
283
514
  };
284
515
  }
285
516
  }
286
- async query(_filter) {
287
- return [];
517
+ /** Headers every call to this Loki carries: tenant + basic auth. */
518
+ authHeaders(config) {
519
+ const headers = {};
520
+ if (config.tenantId.trim().length > 0) headers["X-Scope-OrgID"] = config.tenantId.trim();
521
+ if (config.username.length > 0) headers["authorization"] = `Basic ${Buffer.from(`${config.username}:${config.password}`).toString("base64")}`;
522
+ return headers;
523
+ }
524
+ /**
525
+ * Readable when it is configured (D898): the same URL and credentials the
526
+ * push uses answer `query_range`. Labels are `job`/`node`/`level`, so those
527
+ * filter at the index; device, addon and text are line filters.
528
+ */
529
+ async describeRead() {
530
+ const configured = this.isActive();
531
+ return {
532
+ readable: configured,
533
+ reason: configured ? null : "Loki is not enabled or has no URL",
534
+ filters: {
535
+ deviceId: true,
536
+ addonId: true,
537
+ nodeId: true,
538
+ tags: true,
539
+ level: true,
540
+ text: true,
541
+ timeRange: true
542
+ },
543
+ retentionDays: null
544
+ };
545
+ }
546
+ /** One backward slice from `query_range`, newest first. Never logs. */
547
+ async read(query) {
548
+ const config = this.config;
549
+ if (config === null || !this.isActive()) throw new Error("Loki is not enabled or has no URL");
550
+ const controller = new AbortController();
551
+ const requestId = query.requestId;
552
+ if (requestId !== void 0) this.reads.set(requestId, controller);
553
+ const base = config.url.replace(/\/+$/, "");
554
+ const range = async ({ logql, startNs, endNs, limit }) => {
555
+ const params = new URLSearchParams({
556
+ query: logql,
557
+ start: String(startNs),
558
+ end: String(endNs),
559
+ limit: String(limit),
560
+ direction: "backward"
561
+ });
562
+ const timeout = setTimeout(() => controller.abort(), Math.max(1e3, config.timeoutMs));
563
+ try {
564
+ const response = await this.fetchFn(`${base}/loki/api/v1/query_range?${params.toString()}`, {
565
+ method: "GET",
566
+ headers: this.authHeaders(config),
567
+ signal: controller.signal
568
+ });
569
+ const text = await response.text();
570
+ if (!response.ok) throw new Error(`Loki query failed: ${String(response.status)}${text.length > 0 ? ` — ${text.slice(0, 200)}` : ""}`);
571
+ return {
572
+ rows: parseQueryRange(JSON.parse(text)),
573
+ bytes: Buffer.byteLength(text)
574
+ };
575
+ } finally {
576
+ clearTimeout(timeout);
577
+ }
578
+ };
579
+ try {
580
+ return await readLokiBackward({
581
+ query,
582
+ labels: { job: config.job },
583
+ range,
584
+ isCancelled: () => controller.signal.aborted
585
+ });
586
+ } catch (error) {
587
+ if (controller.signal.aborted) return {
588
+ entries: [],
589
+ nextBefore: query.before ?? null,
590
+ nextAfter: null,
591
+ scannedBytes: 0,
592
+ done: false,
593
+ cancelled: true
594
+ };
595
+ throw error;
596
+ } finally {
597
+ if (requestId !== void 0) this.reads.delete(requestId);
598
+ }
599
+ }
600
+ async cancelRead(input) {
601
+ const controller = this.reads.get(input.requestId);
602
+ controller?.abort();
603
+ return { cancelled: controller !== void 0 };
288
604
  }
289
605
  async shutdown() {
290
606
  this.stopTimer();