@camstack/system 1.2.13 → 1.2.14

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 (58) hide show
  1. package/dist/addon-runner.js +2 -2
  2. package/dist/addon-runner.mjs +2 -2
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +1 -1
  10. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  15. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  17. package/dist/builtins/hub-forwarder/index.js +1 -1
  18. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  19. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  20. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  21. package/dist/builtins/local-network/local-network.addon.js +1 -1
  22. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  23. package/dist/builtins/loki-logging/index.d.ts +4 -0
  24. package/dist/builtins/loki-logging/index.js +445 -0
  25. package/dist/builtins/loki-logging/index.mjs +431 -0
  26. package/dist/builtins/loki-logging/loki-destination.d.ts +88 -0
  27. package/dist/builtins/loki-logging/loki-logging.addon.d.ts +36 -0
  28. package/dist/builtins/loki-logging/loki-payload.d.ts +46 -0
  29. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  30. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  31. package/dist/builtins/platform-probe/index.js +1 -1
  32. package/dist/builtins/platform-probe/index.mjs +1 -1
  33. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  34. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  35. package/dist/builtins/snapshot/index.js +1 -1
  36. package/dist/builtins/snapshot/index.mjs +1 -1
  37. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  38. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +1 -1
  40. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +1 -1
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  43. package/dist/builtins/system-config/system-config.addon.js +1 -1
  44. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  45. package/dist/builtins/winston-logging/index.js +1 -1
  46. package/dist/builtins/winston-logging/index.mjs +1 -1
  47. package/dist/{dist-DpKeSZel.mjs → dist-eOaraBhT.mjs} +21 -1
  48. package/dist/{dist-BQOCTD5b.js → dist-yttC1aUI.js} +26 -0
  49. package/dist/index.d.ts +2 -0
  50. package/dist/index.js +128 -15
  51. package/dist/index.mjs +127 -16
  52. package/dist/kernel/config-schema.d.ts +38 -1
  53. package/dist/logging/log-manager.d.ts +14 -4
  54. package/dist/logging/log-ring-buffer.d.ts +15 -0
  55. package/dist/logging/partitioned-log-buffer.d.ts +49 -6
  56. package/dist/{manifest-python-deps-1I7WdPDD.js → manifest-python-deps-DSrmih6E.js} +1 -1
  57. package/dist/{manifest-python-deps-I2MGvDvo.mjs → manifest-python-deps-bn3qrsxR.mjs} +1 -1
  58. package/package.json +13 -1
@@ -1,6 +1,6 @@
1
1
  const require_chunk = require("./chunk-Cek0wNdY.js");
2
- const require_dist = require("./dist-BQOCTD5b.js");
3
- const require_manifest_python_deps = require("./manifest-python-deps-1I7WdPDD.js");
2
+ const require_dist = require("./dist-yttC1aUI.js");
3
+ const require_manifest_python_deps = require("./manifest-python-deps-DSrmih6E.js");
4
4
  const require_custom_action_registry = require("./custom-action-registry-jY0NOZK8.js");
5
5
  let node_fs = require("node:fs");
6
6
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,5 +1,5 @@
1
- import { N as isCollectionArrayMethod, t as ALL_CAPABILITY_DEFINITIONS } from "./dist-DpKeSZel.mjs";
2
- import { Ct as resolveAddonClass, I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps } from "./manifest-python-deps-I2MGvDvo.mjs";
1
+ import { N as isCollectionArrayMethod, t as ALL_CAPABILITY_DEFINITIONS } from "./dist-eOaraBhT.mjs";
2
+ import { Ct as resolveAddonClass, I as createUdsLoggerWithControl, L as LocalChildClient, at as setWorkerNativeCapsChangeListener, bt as installManifestNativeDeps, i as createUdsAddonContext, nt as getWorkerNativeCapProvider, rt as getWorkerNativeCapSnapshot, st as validateProviderRegistrations, t as installManifestPythonDeps } from "./manifest-python-deps-bn3qrsxR.mjs";
3
3
  import { t as CustomActionRegistry } from "./custom-action-registry-F__gp_VX.mjs";
4
4
  import { register } from "node:module";
5
5
  import * as fs from "node:fs";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { at as errMsg, jt as EventCategory, ot as BaseAddon, p as addonPagesCapability } from "../../dist-DpKeSZel.mjs";
1
+ import { Mt as EventCategory, ot as errMsg, p as addonPagesCapability, st as BaseAddon } from "../../dist-eOaraBhT.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { at as errMsg, h as addonWidgetsCapability, jt as EventCategory, ot as BaseAddon } from "../../dist-DpKeSZel.mjs";
1
+ import { Mt as EventCategory, h as addonWidgetsCapability, ot as errMsg, st as BaseAddon } from "../../dist-eOaraBhT.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  //#region src/builtins/alerts/alerts.addon.ts
8
8
  var CATEGORY_MAPPINGS = {
9
9
  "addon.crashed": {
@@ -1,4 +1,4 @@
1
- import { at as errMsg, g as alertsCapability, jt as EventCategory, n as AlertSchema, ot as BaseAddon, yt as createEvent } from "../../dist-DpKeSZel.mjs";
1
+ import { Mt as EventCategory, bt as createEvent, g as alertsCapability, n as AlertSchema, ot as errMsg, st as BaseAddon } from "../../dist-eOaraBhT.mjs";
2
2
  //#region src/builtins/alerts/alerts.addon.ts
3
3
  var CATEGORY_MAPPINGS = {
4
4
  "addon.crashed": {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_fs$1 = require_chunk.__toESM(node_fs, 1);
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { jt as EventCategory, ot as BaseAddon, y as backupCapability } from "../../dist-DpKeSZel.mjs";
1
+ import { Mt as EventCategory, st as BaseAddon, y as backupCapability } from "../../dist-eOaraBhT.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import Kt from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { ot as BaseAddon, z as logDestinationCapability } from "../../dist-DpKeSZel.mjs";
1
+ import { st as BaseAddon, z as logDestinationCapability } from "../../dist-eOaraBhT.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  let _camstack_types_node = require("@camstack/types/node");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { At as sleep, C as deviceStatusCapability, D as enumerateSchemaFields, E as enumerateItemArrayFields, G as parseStreamParamsFormPatch, O as evaluateLinkExpression, S as deviceStateCapability, U as normalizeUnit, X as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as errMsg, b as buildStreamParamsConfigSchema, dt as DeviceRole, ft as DeviceType, ht as WELL_KNOWN_TAB_MAP, it as validateExpressionSource, j as getByPath, jt as EventCategory, l as STREAM_PROFILE_META, nt as toExpressionValue, o as DeviceStatusSchema, ot as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, ut as DeviceFeature, wt as isDeviceConfigCap, x as deviceManagerCapability } from "../../dist-DpKeSZel.mjs";
1
+ import { C as deviceStatusCapability, D as enumerateSchemaFields, E as enumerateItemArrayFields, K as parseStreamParamsFormPatch, Mt as EventCategory, O as evaluateLinkExpression, S as deviceStateCapability, Tt as isDeviceConfigCap, W as normalizeUnit, Z as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as validateExpressionSource, b as buildStreamParamsConfigSchema, dt as DeviceFeature, ft as DeviceRole, gt as WELL_KNOWN_TAB_MAP, j as getByPath, jt as sleep, l as STREAM_PROFILE_META, o as DeviceStatusSchema, ot as errMsg, pt as DeviceType, rt as toExpressionValue, st as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, x as deviceManagerCapability } from "../../dist-eOaraBhT.mjs";
2
2
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  //#region src/builtins/doorbell/trigger-engine.ts
8
8
  /**
9
9
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -1,4 +1,4 @@
1
- import { at as errMsg, ft as DeviceType, jt as EventCategory, ot as BaseAddon, w as doorbellCapability, yt as createEvent } from "../../dist-DpKeSZel.mjs";
1
+ import { Mt as EventCategory, bt as createEvent, ot as errMsg, pt as DeviceType, st as BaseAddon, w as doorbellCapability } from "../../dist-eOaraBhT.mjs";
2
2
  //#region src/builtins/doorbell/trigger-engine.ts
3
3
  /**
4
4
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { ot as BaseAddon, z as logDestinationCapability } from "../../dist-DpKeSZel.mjs";
1
+ import { st as BaseAddon, z as logDestinationCapability } from "../../dist-eOaraBhT.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-BQOCTD5b.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,4 +1,4 @@
1
- import { f as UserRecordSchema, ot as BaseAddon, r as ApiKeyRecordSchema, rt as userManagementCapability, u as ScopedTokenSchema, v as authProviderCapability } from "../../dist-DpKeSZel.mjs";
1
+ import { f as UserRecordSchema, it as userManagementCapability, r as ApiKeyRecordSchema, st as BaseAddon, u as ScopedTokenSchema, v as authProviderCapability } from "../../dist-eOaraBhT.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import * as crypto$1 from "node:crypto";
4
4
  import { randomUUID } from "node:crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-BQOCTD5b.js");
3
+ const require_dist = require("../../dist-yttC1aUI.js");
4
4
  let node_os = require("node:os");
5
5
  node_os = require_chunk.__toESM(node_os);
6
6
  //#region src/builtins/local-network/local-network.addon.ts
@@ -1,4 +1,4 @@
1
- import { R as localNetworkCapability, jt as EventCategory, ot as BaseAddon } from "../../dist-DpKeSZel.mjs";
1
+ import { Mt as EventCategory, R as localNetworkCapability, st as BaseAddon } from "../../dist-eOaraBhT.mjs";
2
2
  import * as os from "node:os";
3
3
  //#region src/builtins/local-network/local-network.addon.ts
4
4
  /**
@@ -0,0 +1,4 @@
1
+ export { LokiLoggingAddon } from './loki-logging.addon.js';
2
+ export { LokiDestination, type LokiConfig, type LokiStatus } from './loki-destination.js';
3
+ export { buildPushBody, formatLokiLine, parseExtraLabels, sanitizeLabelName, toUnixNano, type LokiPushBody, type LokiStream, } from './loki-payload.js';
4
+ export { LokiLoggingAddon as default } from './loki-logging.addon.js';
@@ -0,0 +1,445 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ require("../../chunk-Cek0wNdY.js");
6
+ const require_dist = require("../../dist-yttC1aUI.js");
7
+ //#region src/builtins/loki-logging/loki-payload.ts
8
+ /**
9
+ * Loki rejects a label name that is not a valid Prometheus label
10
+ * (`[a-zA-Z_][a-zA-Z0-9_]*`), so operator-supplied keys are sanitised rather
11
+ * than passed through — a 400 from the push endpoint on every batch is a much
12
+ * worse outcome than a renamed label.
13
+ */
14
+ function sanitizeLabelName(raw) {
15
+ const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
16
+ return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
17
+ }
18
+ /** Parse an operator's `key=value,key2=value2` label string. */
19
+ function parseExtraLabels(raw) {
20
+ const out = {};
21
+ for (const pair of raw.split(",")) {
22
+ const trimmed = pair.trim();
23
+ if (trimmed.length === 0) continue;
24
+ const eq = trimmed.indexOf("=");
25
+ if (eq <= 0) continue;
26
+ const name = sanitizeLabelName(trimmed.slice(0, eq).trim());
27
+ const value = trimmed.slice(eq + 1).trim();
28
+ if (value.length > 0) out[name] = value;
29
+ }
30
+ return out;
31
+ }
32
+ /** `Date` → Loki's nanosecond-precision string timestamp. */
33
+ function toUnixNano(timestamp) {
34
+ const ms = timestamp instanceof Date ? timestamp.getTime() : typeof timestamp === "number" ? timestamp : Date.parse(timestamp);
35
+ return `${String(Math.trunc(Number.isFinite(ms) ? ms : 0))}000000`;
36
+ }
37
+ /**
38
+ * The line body: JSON carrying everything that must NOT be a label. Loki can
39
+ * parse this with `| json` at query time, so `scope` and `tags.deviceId` stay
40
+ * filterable without ever being indexed.
41
+ */
42
+ function formatLokiLine(entry) {
43
+ const line = { message: entry.message };
44
+ if (entry.scope !== void 0 && entry.scope.length > 0) line["scope"] = entry.scope;
45
+ if (entry.tags !== void 0 && Object.keys(entry.tags).length > 0) line["tags"] = entry.tags;
46
+ if (entry.meta !== void 0 && Object.keys(entry.meta).length > 0) line["meta"] = entry.meta;
47
+ return JSON.stringify(line);
48
+ }
49
+ /**
50
+ * Group a batch into one stream per distinct label set, and sort each stream's
51
+ * values by timestamp.
52
+ *
53
+ * The sort is not cosmetic. Loki historically rejected a stream whose entries
54
+ * were not in ascending time order, and even with out-of-order ingestion enabled
55
+ * it is bounded by a window. Entries reach a destination from several concurrent
56
+ * producers, so a batch is NOT naturally ordered — without this, some pushes
57
+ * fail with `entry out of order` and those log lines are simply gone.
58
+ */
59
+ function buildPushBody(entries, labels) {
60
+ if (entries.length === 0) return null;
61
+ const base = {
62
+ job: labels.job,
63
+ node: labels.node
64
+ };
65
+ for (const [key, value] of Object.entries(labels.extra ?? {})) base[key] = value;
66
+ const byLevel = /* @__PURE__ */ new Map();
67
+ for (const entry of entries) {
68
+ const values = byLevel.get(entry.level) ?? [];
69
+ values.push([toUnixNano(entry.timestamp), formatLokiLine(entry)]);
70
+ byLevel.set(entry.level, values);
71
+ }
72
+ const streams = [];
73
+ for (const [level, values] of byLevel) streams.push({
74
+ stream: {
75
+ ...base,
76
+ level
77
+ },
78
+ values: values.toSorted((a, b) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0)
79
+ });
80
+ return { streams };
81
+ }
82
+ //#endregion
83
+ //#region src/builtins/loki-logging/loki-destination.ts
84
+ var LEVEL_ORDER = {
85
+ debug: 0,
86
+ info: 1,
87
+ warn: 2,
88
+ error: 3
89
+ };
90
+ /**
91
+ * Bounded so an unreachable Loki cannot grow the heap without limit. 10k entries
92
+ * of a few hundred bytes is a handful of MB — enough to ride out a restart of the
93
+ * Loki container, not enough to matter if it never comes back.
94
+ */
95
+ var MAX_QUEUE = 1e4;
96
+ var LokiDestination = class {
97
+ config = null;
98
+ queue = [];
99
+ timer;
100
+ flushing = false;
101
+ pushed = 0;
102
+ dropped = 0;
103
+ failures = 0;
104
+ lastError = null;
105
+ lastPushAtMs = null;
106
+ fetchFn;
107
+ setIntervalFn;
108
+ clearIntervalFn;
109
+ constructor(deps) {
110
+ this.fetchFn = deps?.fetchFn ?? ((url, init) => globalThis.fetch(url, {
111
+ method: init.method,
112
+ headers: { ...init.headers },
113
+ body: init.body,
114
+ ...init.signal ? { signal: init.signal } : {}
115
+ }));
116
+ this.setIntervalFn = deps?.setIntervalFn ?? setInterval;
117
+ this.clearIntervalFn = deps?.clearIntervalFn ?? clearInterval;
118
+ }
119
+ async initialize(config) {
120
+ this.config = config ?? null;
121
+ if (!this.isActive()) return;
122
+ this.timer = this.setIntervalFn(() => {
123
+ this.flush();
124
+ }, Math.max(250, config?.batchIntervalMs ?? 5e3));
125
+ const timer = this.timer;
126
+ if (typeof timer === "object" && timer !== null && "unref" in timer) timer.unref();
127
+ }
128
+ /** Enabled AND pointed somewhere. An enabled destination with no URL is inert. */
129
+ isActive() {
130
+ return this.config !== null && this.config.enabled && this.config.url.trim().length > 0;
131
+ }
132
+ write(entry) {
133
+ if (!this.isActive() || this.config === null) return;
134
+ if (LEVEL_ORDER[entry.level] < LEVEL_ORDER[this.config.level]) return;
135
+ this.queue.push(entry);
136
+ if (this.queue.length > MAX_QUEUE) {
137
+ const overflow = this.queue.length - MAX_QUEUE;
138
+ this.queue.splice(0, overflow);
139
+ this.dropped += overflow;
140
+ }
141
+ if (this.queue.length >= this.config.batchSize) this.flush();
142
+ }
143
+ /**
144
+ * Push whatever is queued. Never throws and never logs.
145
+ *
146
+ * On failure the batch is put BACK at the front of the queue so a transient
147
+ * Loki outage does not lose lines; `MAX_QUEUE` is what stops that from growing
148
+ * without bound.
149
+ */
150
+ async flush() {
151
+ if (this.flushing || !this.isActive() || this.config === null) return;
152
+ if (this.queue.length === 0) return;
153
+ this.flushing = true;
154
+ const batch = this.queue;
155
+ this.queue = [];
156
+ try {
157
+ const body = buildPushBody(batch, {
158
+ job: this.config.job,
159
+ node: this.config.node,
160
+ extra: parseExtraLabels(this.config.extraLabels)
161
+ });
162
+ if (body !== null) {
163
+ await this.push(body, this.config);
164
+ this.pushed += batch.length;
165
+ this.lastPushAtMs = Date.now();
166
+ this.lastError = null;
167
+ }
168
+ } catch (error) {
169
+ this.failures += 1;
170
+ this.lastError = error instanceof Error ? error.message : String(error);
171
+ this.queue = [...batch, ...this.queue];
172
+ if (this.queue.length > MAX_QUEUE) {
173
+ const overflow = this.queue.length - MAX_QUEUE;
174
+ this.queue.splice(0, overflow);
175
+ this.dropped += overflow;
176
+ }
177
+ } finally {
178
+ this.flushing = false;
179
+ }
180
+ }
181
+ async push(body, config) {
182
+ const endpoint = `${config.url.replace(/\/+$/, "")}/loki/api/v1/push`;
183
+ const headers = { "content-type": "application/json" };
184
+ if (config.tenantId.trim().length > 0) headers["X-Scope-OrgID"] = config.tenantId.trim();
185
+ if (config.username.length > 0) headers["authorization"] = `Basic ${Buffer.from(`${config.username}:${config.password}`).toString("base64")}`;
186
+ const controller = new AbortController();
187
+ const timeout = setTimeout(() => controller.abort(), Math.max(1e3, config.timeoutMs));
188
+ try {
189
+ const response = await this.fetchFn(endpoint, {
190
+ method: "POST",
191
+ headers,
192
+ body: JSON.stringify(body),
193
+ signal: controller.signal
194
+ });
195
+ if (!response.ok) {
196
+ const detail = await response.text().catch(() => "");
197
+ throw new Error(`Loki push failed: ${String(response.status)}${detail.length > 0 ? ` — ${detail.slice(0, 200)}` : ""}`);
198
+ }
199
+ } finally {
200
+ clearTimeout(timeout);
201
+ }
202
+ }
203
+ getStatus() {
204
+ return {
205
+ enabled: this.config?.enabled === true,
206
+ configured: this.isActive(),
207
+ queued: this.queue.length,
208
+ pushed: this.pushed,
209
+ dropped: this.dropped,
210
+ failures: this.failures,
211
+ lastError: this.lastError,
212
+ lastPushAtMs: this.lastPushAtMs
213
+ };
214
+ }
215
+ /**
216
+ * One-shot reachability probe for the Admin UI's "Test connection" button.
217
+ * Pushes a single synthetic entry, which validates the URL, the auth, the
218
+ * tenant header AND the label policy in one go — a bare GET would validate
219
+ * none of them.
220
+ */
221
+ async testConnection(config) {
222
+ if (config.url.trim().length === 0) return {
223
+ ok: false,
224
+ error: "No Loki URL configured"
225
+ };
226
+ const body = buildPushBody([{
227
+ timestamp: /* @__PURE__ */ new Date(),
228
+ level: "info",
229
+ scope: "loki-logging",
230
+ message: "CamStack connection test"
231
+ }], {
232
+ job: config.job,
233
+ node: config.node,
234
+ extra: parseExtraLabels(config.extraLabels)
235
+ });
236
+ if (body === null) return {
237
+ ok: false,
238
+ error: "Nothing to send"
239
+ };
240
+ try {
241
+ await this.push(body, config);
242
+ return {
243
+ ok: true,
244
+ error: null
245
+ };
246
+ } catch (error) {
247
+ return {
248
+ ok: false,
249
+ error: error instanceof Error ? error.message : String(error)
250
+ };
251
+ }
252
+ }
253
+ async query(_filter) {
254
+ return [];
255
+ }
256
+ async shutdown() {
257
+ if (this.timer !== void 0) {
258
+ this.clearIntervalFn(this.timer);
259
+ this.timer = void 0;
260
+ }
261
+ await this.flush();
262
+ this.config = null;
263
+ }
264
+ };
265
+ //#endregion
266
+ //#region src/builtins/loki-logging/loki-logging.addon.ts
267
+ var DEFAULTS = {
268
+ enabled: false,
269
+ url: "",
270
+ job: "camstack",
271
+ level: "info",
272
+ batchIntervalMs: 5e3,
273
+ batchSize: 200,
274
+ extraLabels: "",
275
+ tenantId: "",
276
+ username: "",
277
+ password: "",
278
+ timeoutMs: 1e4
279
+ };
280
+ var LokiLoggingAddon = class extends require_dist.BaseAddon {
281
+ destination = null;
282
+ constructor() {
283
+ super({ ...DEFAULTS });
284
+ }
285
+ async onInitialize() {
286
+ this.destination = new LokiDestination();
287
+ await this.destination.initialize(this.destinationConfig());
288
+ this.ctx.logger.info(this.config.enabled && this.config.url.length > 0 ? `Loki logging initialized → ${this.config.url}` : "Loki logging registered but inactive (set a URL and enable it)");
289
+ return [{
290
+ capability: require_dist.logDestinationCapability,
291
+ provider: this.destination
292
+ }];
293
+ }
294
+ async onShutdown() {
295
+ await this.destination?.shutdown();
296
+ this.destination = null;
297
+ }
298
+ /**
299
+ * `node` is a Loki LABEL, so it must be the node's real identity and must not
300
+ * be operator-editable — a typo would fork the stream and split a node's history
301
+ * in two. `kernel.localNodeId` is the Moleculer node id (`'hub'` on the hub, the
302
+ * agent's own name elsewhere), which is exactly the bounded value wanted here.
303
+ *
304
+ * NOT named `resolveConfig`: `BaseAddon` already has a protected
305
+ * `resolveConfig()` that reloads persisted settings, and shadowing it with a
306
+ * different signature is a type error — usefully, since silently overriding it
307
+ * would have broken this addon's settings reload.
308
+ */
309
+ destinationConfig() {
310
+ return {
311
+ ...this.config,
312
+ node: this.ctx.kernel.localNodeId ?? "hub"
313
+ };
314
+ }
315
+ getDestination() {
316
+ if (!this.destination) throw new Error("Loki logging not initialized");
317
+ return this.destination;
318
+ }
319
+ globalSettingsSchema() {
320
+ return this.schema({ sections: [{
321
+ id: "loki-logging",
322
+ title: "Loki",
323
+ description: "Ship this node’s logs to a Grafana Loki instance. Independent of the rotated-file and console destinations — enabling this does not disable those.",
324
+ columns: 2,
325
+ fields: [
326
+ this.field({
327
+ type: "boolean",
328
+ key: "enabled",
329
+ label: "Enabled",
330
+ description: "Off by default. Needs a URL below to do anything.",
331
+ default: false
332
+ }),
333
+ this.field({
334
+ type: "text",
335
+ key: "url",
336
+ label: "Loki URL",
337
+ description: "Base URL, e.g. http://loki:3100 — the /loki/api/v1/push path is added.",
338
+ placeholder: "http://loki:3100",
339
+ default: ""
340
+ }),
341
+ this.field({
342
+ type: "select",
343
+ key: "level",
344
+ label: "Minimum level",
345
+ default: "info",
346
+ options: [
347
+ {
348
+ value: "debug",
349
+ label: "Debug"
350
+ },
351
+ {
352
+ value: "info",
353
+ label: "Info"
354
+ },
355
+ {
356
+ value: "warn",
357
+ label: "Warn"
358
+ },
359
+ {
360
+ value: "error",
361
+ label: "Error"
362
+ }
363
+ ]
364
+ }),
365
+ this.field({
366
+ type: "text",
367
+ key: "job",
368
+ label: "Job label",
369
+ description: "The Loki `job` label. Keep it fixed — labels are indexed.",
370
+ default: "camstack"
371
+ }),
372
+ this.field({
373
+ type: "text",
374
+ key: "extraLabels",
375
+ label: "Extra labels",
376
+ description: "Static `key=value,key2=value2`. Low-cardinality only: per-entry values belong in the line body, and a high-cardinality label will bring Loki down.",
377
+ placeholder: "env=home,site=garage",
378
+ default: ""
379
+ }),
380
+ this.field({
381
+ type: "text",
382
+ key: "tenantId",
383
+ label: "Tenant ID",
384
+ description: "Sent as X-Scope-OrgID. Required by Grafana Cloud and multi-tenant Loki.",
385
+ default: ""
386
+ }),
387
+ this.field({
388
+ type: "text",
389
+ key: "username",
390
+ label: "Username",
391
+ description: "HTTP basic auth. Leave empty for none.",
392
+ default: ""
393
+ }),
394
+ this.field({
395
+ type: "password",
396
+ key: "password",
397
+ label: "Password",
398
+ default: ""
399
+ }),
400
+ this.field({
401
+ type: "number",
402
+ key: "batchIntervalMs",
403
+ label: "Flush interval",
404
+ description: "How often a partial batch is pushed.",
405
+ min: 250,
406
+ max: 6e4,
407
+ step: 250,
408
+ default: 5e3,
409
+ unit: "ms"
410
+ }),
411
+ this.field({
412
+ type: "number",
413
+ key: "batchSize",
414
+ label: "Batch size",
415
+ description: "Push early once this many entries are queued.",
416
+ min: 1,
417
+ max: 5e3,
418
+ step: 10,
419
+ default: 200
420
+ }),
421
+ this.field({
422
+ type: "number",
423
+ key: "timeoutMs",
424
+ label: "Request timeout",
425
+ min: 1e3,
426
+ max: 6e4,
427
+ step: 500,
428
+ default: 1e4,
429
+ unit: "ms"
430
+ })
431
+ ]
432
+ }] });
433
+ }
434
+ };
435
+ //#endregion
436
+ exports.LokiDestination = LokiDestination;
437
+ exports.LokiDestination$1 = LokiDestination;
438
+ exports.LokiLoggingAddon = LokiLoggingAddon;
439
+ exports.LokiLoggingAddon$1 = LokiLoggingAddon;
440
+ exports.buildPushBody = buildPushBody;
441
+ exports.default = LokiLoggingAddon;
442
+ exports.formatLokiLine = formatLokiLine;
443
+ exports.parseExtraLabels = parseExtraLabels;
444
+ exports.sanitizeLabelName = sanitizeLabelName;
445
+ exports.toUnixNano = toUnixNano;