@camstack/system 1.0.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.
Files changed (262) hide show
  1. package/dist/addon/addon-api-factory.d.ts +35 -0
  2. package/dist/addon-routes/addon-route-registry.d.ts +37 -0
  3. package/dist/addon-runner.js +599 -0
  4. package/dist/addon-runner.mjs +597 -0
  5. package/dist/auth/api-key-manager.d.ts +26 -0
  6. package/dist/auth/auth-manager.d.ts +109 -0
  7. package/dist/auth/parse-record.d.ts +18 -0
  8. package/dist/auth/scope-matcher.d.ts +7 -0
  9. package/dist/auth/scoped-token-manager.d.ts +40 -0
  10. package/dist/auth/totp-manager.d.ts +51 -0
  11. package/dist/auth/user-manager.d.ts +34 -0
  12. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.d.ts +53 -0
  13. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +259 -0
  14. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +251 -0
  15. package/dist/builtins/addon-pages-aggregator/dedupe-pages.d.ts +6 -0
  16. package/dist/builtins/addon-pages-aggregator/index.d.ts +1 -0
  17. package/dist/builtins/addon-pages-aggregator/index.js +8 -0
  18. package/dist/builtins/addon-pages-aggregator/index.mjs +2 -0
  19. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts +47 -0
  20. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +228 -0
  21. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +220 -0
  22. package/dist/builtins/addon-widgets-aggregator/index.d.ts +1 -0
  23. package/dist/builtins/addon-widgets-aggregator/index.js +8 -0
  24. package/dist/builtins/addon-widgets-aggregator/index.mjs +2 -0
  25. package/dist/builtins/alerts/alerts.addon.d.ts +81 -0
  26. package/dist/builtins/alerts/alerts.addon.js +601 -0
  27. package/dist/builtins/alerts/alerts.addon.mjs +595 -0
  28. package/dist/builtins/alerts/index.d.ts +1 -0
  29. package/dist/builtins/alerts/index.js +4 -0
  30. package/dist/builtins/alerts/index.mjs +2 -0
  31. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +147 -0
  32. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2229 -0
  33. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2220 -0
  34. package/dist/builtins/backup-orchestrator/cron-helpers.d.ts +23 -0
  35. package/dist/builtins/backup-orchestrator/destination-policy.d.ts +72 -0
  36. package/dist/builtins/backup-orchestrator/download-helpers.d.ts +12 -0
  37. package/dist/builtins/backup-orchestrator/index.d.ts +2 -0
  38. package/dist/builtins/backup-orchestrator/index.js +8 -0
  39. package/dist/builtins/backup-orchestrator/index.mjs +2 -0
  40. package/dist/builtins/backup-orchestrator/manifest-store.d.ts +77 -0
  41. package/dist/builtins/console-logging/console-destination.d.ts +13 -0
  42. package/dist/builtins/console-logging/console-logging.addon.d.ts +25 -0
  43. package/dist/builtins/console-logging/index.d.ts +3 -0
  44. package/dist/builtins/console-logging/index.js +104 -0
  45. package/dist/builtins/console-logging/index.mjs +95 -0
  46. package/dist/builtins/device-manager/device-config-contribution.d.ts +32 -0
  47. package/dist/builtins/device-manager/device-event-propagator.d.ts +26 -0
  48. package/dist/builtins/device-manager/device-link-overlay.d.ts +23 -0
  49. package/dist/builtins/device-manager/device-link-resolver.d.ts +15 -0
  50. package/dist/builtins/device-manager/device-manager.addon.d.ts +452 -0
  51. package/dist/builtins/device-manager/device-manager.addon.js +3299 -0
  52. package/dist/builtins/device-manager/device-manager.addon.mjs +3292 -0
  53. package/dist/builtins/device-manager/index.d.ts +2 -0
  54. package/dist/builtins/device-manager/index.js +8 -0
  55. package/dist/builtins/device-manager/index.mjs +2 -0
  56. package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +44 -0
  57. package/dist/builtins/hub-forwarder/hub-forwarder.addon.d.ts +15 -0
  58. package/dist/builtins/hub-forwarder/index.d.ts +3 -0
  59. package/dist/builtins/hub-forwarder/index.js +154 -0
  60. package/dist/builtins/hub-forwarder/index.mjs +145 -0
  61. package/dist/builtins/local-auth/auth-schema.d.ts +26 -0
  62. package/dist/builtins/local-auth/index.d.ts +1 -0
  63. package/dist/builtins/local-auth/index.js +4 -0
  64. package/dist/builtins/local-auth/index.mjs +2 -0
  65. package/dist/builtins/local-auth/local-auth.addon.d.ts +18 -0
  66. package/dist/builtins/local-auth/local-auth.addon.js +8094 -0
  67. package/dist/builtins/local-auth/local-auth.addon.mjs +8063 -0
  68. package/dist/builtins/local-auth/oauth-grants.d.ts +45 -0
  69. package/dist/builtins/local-auth/oauth-session-manager.d.ts +50 -0
  70. package/dist/builtins/local-network/index.d.ts +2 -0
  71. package/dist/builtins/local-network/index.js +10 -0
  72. package/dist/builtins/local-network/index.mjs +2 -0
  73. package/dist/builtins/local-network/local-network.addon.d.ts +150 -0
  74. package/dist/builtins/local-network/local-network.addon.js +489 -0
  75. package/dist/builtins/local-network/local-network.addon.mjs +477 -0
  76. package/dist/builtins/native-metrics/index.d.ts +2 -0
  77. package/dist/builtins/native-metrics/native-metrics-provider.d.ts +48 -0
  78. package/dist/builtins/native-metrics/native-metrics.addon.d.ts +73 -0
  79. package/dist/builtins/native-metrics/native-metrics.addon.js +922 -0
  80. package/dist/builtins/native-metrics/native-metrics.addon.mjs +914 -0
  81. package/dist/builtins/platform-probe/hardware-decode-accel-probe.d.ts +37 -0
  82. package/dist/builtins/platform-probe/hardware-encoder-probe.d.ts +13 -0
  83. package/dist/builtins/platform-probe/index.d.ts +22 -0
  84. package/dist/builtins/platform-probe/index.js +834 -0
  85. package/dist/builtins/platform-probe/index.mjs +822 -0
  86. package/dist/builtins/platform-probe/inference-config-resolver.d.ts +29 -0
  87. package/dist/builtins/platform-probe/intel-accelerators.d.ts +11 -0
  88. package/dist/builtins/platform-probe/platform-scorer.d.ts +30 -0
  89. package/dist/builtins/platform-probe/runtime-packages.d.ts +6 -0
  90. package/dist/builtins/remote-access-orchestrator/enabled-providers-reconcile.d.ts +96 -0
  91. package/dist/builtins/remote-access-orchestrator/index.d.ts +1 -0
  92. package/dist/builtins/remote-access-orchestrator/index.js +8 -0
  93. package/dist/builtins/remote-access-orchestrator/index.mjs +2 -0
  94. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +40 -0
  95. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +214 -0
  96. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +208 -0
  97. package/dist/builtins/shared/settle-sources.d.ts +22 -0
  98. package/dist/builtins/snapshot/index.d.ts +2 -0
  99. package/dist/builtins/snapshot/index.js +494 -0
  100. package/dist/builtins/snapshot/index.mjs +488 -0
  101. package/dist/builtins/snapshot/snapshot.addon.d.ts +120 -0
  102. package/dist/builtins/sqlite-storage/config-store.d.ts +8 -0
  103. package/dist/builtins/sqlite-storage/device-store.d.ts +23 -0
  104. package/dist/builtins/sqlite-storage/filesystem-browse-provider.d.ts +25 -0
  105. package/dist/builtins/sqlite-storage/filesystem-storage-provider.d.ts +83 -0
  106. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +32 -0
  107. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +396 -0
  108. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +388 -0
  109. package/dist/builtins/sqlite-storage/index.d.ts +8 -0
  110. package/dist/builtins/sqlite-storage/index.js +62 -0
  111. package/dist/builtins/sqlite-storage/index.mjs +49 -0
  112. package/dist/builtins/sqlite-storage/integration-registry.d.ts +27 -0
  113. package/dist/builtins/sqlite-storage/path-guard.d.ts +4 -0
  114. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +102 -0
  115. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +14 -0
  116. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +644 -0
  117. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +636 -0
  118. package/dist/builtins/storage-orchestrator/index.d.ts +6 -0
  119. package/dist/builtins/storage-orchestrator/index.js +10 -0
  120. package/dist/builtins/storage-orchestrator/index.mjs +2 -0
  121. package/dist/builtins/storage-orchestrator/location-store.d.ts +49 -0
  122. package/dist/builtins/storage-orchestrator/provider-discovery.d.ts +10 -0
  123. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +103 -0
  124. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1138 -0
  125. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1128 -0
  126. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +236 -0
  127. package/dist/builtins/storage-orchestrator/storage-pressure-manager.d.ts +38 -0
  128. package/dist/builtins/system-backup/system-backup.service.d.ts +137 -0
  129. package/dist/builtins/system-config/index.d.ts +1 -0
  130. package/dist/builtins/system-config/index.js +8 -0
  131. package/dist/builtins/system-config/index.mjs +2 -0
  132. package/dist/builtins/system-config/system-config.addon.d.ts +10 -0
  133. package/dist/builtins/system-config/system-config.addon.js +232 -0
  134. package/dist/builtins/system-config/system-config.addon.mjs +226 -0
  135. package/dist/builtins/winston-logging/index.d.ts +3 -0
  136. package/dist/builtins/winston-logging/index.js +156 -0
  137. package/dist/builtins/winston-logging/index.mjs +144 -0
  138. package/dist/builtins/winston-logging/winston-destination.d.ts +21 -0
  139. package/dist/builtins/winston-logging/winston-logging.addon.d.ts +19 -0
  140. package/dist/chunk-CNf5ZN-e.mjs +37 -0
  141. package/dist/chunk-Cek0wNdY.js +64 -0
  142. package/dist/download/model-download-service.d.ts +41 -0
  143. package/dist/download/model-downloader.d.ts +31 -0
  144. package/dist/events/event-bus.d.ts +10 -0
  145. package/dist/events/system-event-bus.d.ts +14 -0
  146. package/dist/feature/feature-manager.d.ts +11 -0
  147. package/dist/formatter-B7qW8bPJ.mjs +162 -0
  148. package/dist/formatter-DqAKDlvN.js +167 -0
  149. package/dist/http/authenticated-file-server.d.ts +53 -0
  150. package/dist/http/data-plane-registry.d.ts +23 -0
  151. package/dist/http/file-data-plane.d.ts +10 -0
  152. package/dist/http/reverse-proxy.d.ts +15 -0
  153. package/dist/index.d.ts +82 -0
  154. package/dist/index.js +93485 -0
  155. package/dist/index.mjs +93179 -0
  156. package/dist/intel-accelerators-Gg0P5mnl.js +20 -0
  157. package/dist/intel-accelerators-hGgpZ0pX.mjs +19 -0
  158. package/dist/kernel/addon-class-resolver.d.ts +4 -0
  159. package/dist/kernel/addon-engine-manager.d.ts +22 -0
  160. package/dist/kernel/addon-health-monitor.d.ts +154 -0
  161. package/dist/kernel/addon-installer.d.ts +208 -0
  162. package/dist/kernel/addon-loader.d.ts +106 -0
  163. package/dist/kernel/addon-manifest.d.ts +77 -0
  164. package/dist/kernel/capability-handle.d.ts +46 -0
  165. package/dist/kernel/capability-registry.d.ts +412 -0
  166. package/dist/kernel/config-manager.d.ts +212 -0
  167. package/dist/kernel/config-schema.d.ts +93 -0
  168. package/dist/kernel/custom-action-registry.d.ts +23 -0
  169. package/dist/kernel/deps/addon-deps-manager.d.ts +19 -0
  170. package/dist/kernel/deps/manifest-native-deps.d.ts +25 -0
  171. package/dist/kernel/deps/manifest-python-deps.d.ts +20 -0
  172. package/dist/kernel/device-registry.d.ts +29 -0
  173. package/dist/kernel/fs-utils.d.ts +41 -0
  174. package/dist/kernel/hwaccel/hwaccel-resolver.d.ts +19 -0
  175. package/dist/kernel/hwaccel/hwaccel-service.d.ts +4 -0
  176. package/dist/kernel/index.d.ts +74 -0
  177. package/dist/kernel/infra-capabilities.d.ts +13 -0
  178. package/dist/kernel/moleculer/addon-context-factory.d.ts +91 -0
  179. package/dist/kernel/moleculer/addon-data-plane-facility.d.ts +19 -0
  180. package/dist/kernel/moleculer/addon-runner.d.ts +1 -0
  181. package/dist/kernel/moleculer/addon-service-factory.d.ts +50 -0
  182. package/dist/kernel/moleculer/broker-factory.d.ts +50 -0
  183. package/dist/kernel/moleculer/cap-usage-registry.d.ts +46 -0
  184. package/dist/kernel/moleculer/capabilities-access.d.ts +21 -0
  185. package/dist/kernel/moleculer/child-addon-call-dispatch.d.ts +46 -0
  186. package/dist/kernel/moleculer/child-cap-dispatch.d.ts +20 -0
  187. package/dist/kernel/moleculer/cluster-secret.d.ts +15 -0
  188. package/dist/kernel/moleculer/core-cap-service.d.ts +50 -0
  189. package/dist/kernel/moleculer/crash-supervisor.d.ts +50 -0
  190. package/dist/kernel/moleculer/device-cap-proxy.d.ts +79 -0
  191. package/dist/kernel/moleculer/event-bus-core.d.ts +53 -0
  192. package/dist/kernel/moleculer/event-bus.d.ts +53 -0
  193. package/dist/kernel/moleculer/hub-log-forwarder.d.ts +36 -0
  194. package/dist/kernel/moleculer/hub-service.d.ts +35 -0
  195. package/dist/kernel/moleculer/node-registry.d.ts +126 -0
  196. package/dist/kernel/moleculer/process-context.d.ts +4 -0
  197. package/dist/kernel/moleculer/process-service.d.ts +72 -0
  198. package/dist/kernel/moleculer/provider-registry.d.ts +28 -0
  199. package/dist/kernel/moleculer/readiness-context.d.ts +62 -0
  200. package/dist/kernel/moleculer/readiness-service.d.ts +7 -0
  201. package/dist/kernel/moleculer/register-node-client.d.ts +35 -0
  202. package/dist/kernel/moleculer/remote-logger.d.ts +43 -0
  203. package/dist/kernel/moleculer/resilient-cap-call.d.ts +28 -0
  204. package/dist/kernel/moleculer/stream-probe-service.d.ts +9 -0
  205. package/dist/kernel/moleculer/trpc-links.d.ts +189 -0
  206. package/dist/kernel/moleculer/typed-array-serde.d.ts +25 -0
  207. package/dist/kernel/moleculer/worker-device-restore.d.ts +10 -0
  208. package/dist/kernel/provider-kind-drift.d.ts +12 -0
  209. package/dist/kernel/restart-coordinator.d.ts +90 -0
  210. package/dist/kernel/storage-location-registry.d.ts +40 -0
  211. package/dist/kernel/transport/cap-action-name.d.ts +100 -0
  212. package/dist/kernel/transport/cap-route-resolver.d.ts +148 -0
  213. package/dist/kernel/transport/cap-route.d.ts +148 -0
  214. package/dist/kernel/transport/child-cap-protocol.d.ts +136 -0
  215. package/dist/kernel/transport/create-local-transport.d.ts +7 -0
  216. package/dist/kernel/transport/frame-codec.d.ts +7 -0
  217. package/dist/kernel/transport/index.d.ts +27 -0
  218. package/dist/kernel/transport/local-child-client.d.ts +136 -0
  219. package/dist/kernel/transport/local-child-registry.d.ts +179 -0
  220. package/dist/kernel/transport/local-endpoint-path.d.ts +6 -0
  221. package/dist/kernel/transport/local-transport.d.ts +46 -0
  222. package/dist/kernel/transport/parent-unowned-call.d.ts +75 -0
  223. package/dist/kernel/transport/socket-channel.d.ts +27 -0
  224. package/dist/kernel/transport/uds-event-bridge.d.ts +36 -0
  225. package/dist/kernel/transport/uds-event-bus.d.ts +22 -0
  226. package/dist/kernel/transport/uds-local-transport.d.ts +18 -0
  227. package/dist/kernel/transport/uds-log-ingest.d.ts +28 -0
  228. package/dist/kernel/transport/uds-logger.d.ts +44 -0
  229. package/dist/kernel/utils/ring-buffer.d.ts +15 -0
  230. package/dist/kernel/workspace-detect.d.ts +9 -0
  231. package/dist/lifecycle/lifecycle-state-machine.d.ts +28 -0
  232. package/dist/logging/formatter.d.ts +30 -0
  233. package/dist/logging/log-manager.d.ts +54 -0
  234. package/dist/logging/log-ring-buffer.d.ts +47 -0
  235. package/dist/logging/partitioned-log-buffer.d.ts +35 -0
  236. package/dist/logging/scoped-logger.d.ts +17 -0
  237. package/dist/main-DNnMW7Z2.js +9983 -0
  238. package/dist/main-rtjOwPBR.mjs +9976 -0
  239. package/dist/manifest-python-deps-D1DbAQEv.js +6724 -0
  240. package/dist/manifest-python-deps-DZsKTbs1.mjs +6315 -0
  241. package/dist/network/network-quality.d.ts +11 -0
  242. package/dist/notification/notification-service.d.ts +37 -0
  243. package/dist/notification/toast-service.d.ts +22 -0
  244. package/dist/pipeline/engine-manager-resolver.d.ts +15 -0
  245. package/dist/pipeline/pipeline-runner.d.ts +8 -0
  246. package/dist/pipeline/pipeline-validator.d.ts +13 -0
  247. package/dist/process/resource-monitor.d.ts +11 -0
  248. package/dist/python/python-env-manager.d.ts +12 -0
  249. package/dist/repl/interfaces.d.ts +31 -0
  250. package/dist/repl/repl-engine.d.ts +8 -0
  251. package/dist/resource-monitor-ClDGFyf6.mjs +57 -0
  252. package/dist/resource-monitor-IIEanuJt.js +74 -0
  253. package/dist/settle-sources-Bhsy57y-.js +38 -0
  254. package/dist/settle-sources-CDtNC8ub.mjs +33 -0
  255. package/dist/storage/fs-storage-backend.d.ts +40 -0
  256. package/dist/storage/storage-location-manager.d.ts +23 -0
  257. package/dist/storage/storage-manager.d.ts +83 -0
  258. package/dist/tar-BgAEMRBR.js +5434 -0
  259. package/dist/tar-ByMOPNM0.mjs +5429 -0
  260. package/dist/tls/cert-manager.d.ts +26 -0
  261. package/dist/tls/index.d.ts +1 -0
  262. package/package.json +343 -0
@@ -0,0 +1,388 @@
1
+ import * as fs from "node:fs";
2
+ import * as path$1 from "node:path";
3
+ import { basename, dirname, join, resolve, sep } from "node:path";
4
+ import { BaseAddon, filesystemBrowseCapability, storageProviderCapability } from "@camstack/types";
5
+ import { randomUUID } from "node:crypto";
6
+ import { homedir } from "node:os";
7
+ import { mkdir, readdir, realpath, statfs } from "node:fs/promises";
8
+ //#region src/builtins/sqlite-storage/filesystem-storage-provider.ts
9
+ /**
10
+ * Filesystem `storage-provider` (Task 7).
11
+ *
12
+ * Implements the `storage-provider` collection cap. Replaces the legacy
13
+ * sync `IStorageProvider` shape — every method now takes the live
14
+ * `StorageLocation` inline (no per-provider location state) and works
15
+ * off `location.config.basePath`.
16
+ *
17
+ * Adds chunked upload (`.partial` rename trick) + chunked download
18
+ * sessions for moving multi-GB archives without buffering the whole
19
+ * archive in memory. Sessions auto-expire after 5 minutes idle.
20
+ *
21
+ * The legacy `FilesystemStorageProvider` in `@camstack/types/node` is
22
+ * deliberately untouched — it still backs the sync `ctx.kernel.storage`
23
+ * helper that addons like `pipeline-analytics` use to derive paths
24
+ * synchronously. That sync API will be retired in a later refactor;
25
+ * Task 7 is scoped to the cap surface only.
26
+ */
27
+ /** Idle TTL for upload + download sessions. */
28
+ var SESSION_IDLE_TTL_MS = 300 * 1e3;
29
+ /**
30
+ * Resolve `location.config.basePath` into a string. Throws an actionable
31
+ * error when the field is missing or wrong-typed — every primitive
32
+ * funnels through here, so a missing field surfaces as a single clear
33
+ * message instead of a downstream `path.join` TypeError.
34
+ */
35
+ function getBasePath(location) {
36
+ const basePath = location.config["basePath"];
37
+ if (typeof basePath !== "string" || basePath.length === 0) throw new Error(`filesystem-storage: location "${location.id}" missing string config.basePath`);
38
+ return basePath;
39
+ }
40
+ /**
41
+ * Resolve a relative path against the location's base, rejecting any
42
+ * traversal that escapes the base directory. Mirrors the
43
+ * `addon-asset` endpoint security check in `main.ts`: normalize both
44
+ * the base + the joined target, then enforce `target.startsWith(base + sep)`
45
+ * (with the `+ sep` guard so `/data` doesn't accidentally allow
46
+ * `/datasecret`).
47
+ */
48
+ function safeResolve(base, relativePath) {
49
+ const normalizedBase = path$1.resolve(base);
50
+ const target = path$1.resolve(normalizedBase, relativePath);
51
+ if (target !== normalizedBase && !target.startsWith(normalizedBase + path$1.sep)) throw new Error(`filesystem-storage: relativePath "${relativePath}" escapes basePath "${normalizedBase}"`);
52
+ return target;
53
+ }
54
+ var FilesystemStorageProvider = class FilesystemStorageProvider {
55
+ static providerId = "filesystem-storage";
56
+ static displayName = "Filesystem";
57
+ uploads = /* @__PURE__ */ new Map();
58
+ downloads = /* @__PURE__ */ new Map();
59
+ async getProviderInfo() {
60
+ return {
61
+ providerId: FilesystemStorageProvider.providerId,
62
+ displayName: FilesystemStorageProvider.displayName,
63
+ shouldSaveDiskSpace: true,
64
+ minFreePercent: 10,
65
+ nodeLocal: true,
66
+ configSchema: { sections: [{
67
+ id: "filesystem-storage",
68
+ title: "Filesystem",
69
+ fields: [{
70
+ type: "text",
71
+ key: "basePath",
72
+ label: "Base path",
73
+ description: "Absolute directory path where files are stored.",
74
+ required: true
75
+ }]
76
+ }] }
77
+ };
78
+ }
79
+ async testLocation({ config }) {
80
+ const basePath = config["basePath"];
81
+ if (typeof basePath !== "string" || basePath.length === 0) return {
82
+ ok: false,
83
+ error: "config.basePath must be a non-empty string"
84
+ };
85
+ try {
86
+ const stat = await fs.promises.stat(basePath).catch(() => null);
87
+ if (stat) {
88
+ if (!stat.isDirectory()) return {
89
+ ok: false,
90
+ error: `Path "${basePath}" exists but is not a directory`
91
+ };
92
+ await fs.promises.access(basePath, fs.constants.W_OK);
93
+ return { ok: true };
94
+ }
95
+ await fs.promises.mkdir(basePath, { recursive: true });
96
+ return { ok: true };
97
+ } catch (err) {
98
+ return {
99
+ ok: false,
100
+ error: err instanceof Error ? err.message : String(err)
101
+ };
102
+ }
103
+ }
104
+ async resolve({ location, relativePath }) {
105
+ return safeResolve(getBasePath(location), relativePath);
106
+ }
107
+ async write({ location, relativePath, data }) {
108
+ const filePath = safeResolve(getBasePath(location), relativePath);
109
+ await fs.promises.mkdir(path$1.dirname(filePath), { recursive: true });
110
+ await fs.promises.writeFile(filePath, data);
111
+ }
112
+ async read({ location, relativePath }) {
113
+ const filePath = safeResolve(getBasePath(location), relativePath);
114
+ const buf = await fs.promises.readFile(filePath);
115
+ const out = new Uint8Array(new ArrayBuffer(buf.byteLength));
116
+ out.set(buf);
117
+ return out;
118
+ }
119
+ async exists({ location, relativePath }) {
120
+ const filePath = safeResolve(getBasePath(location), relativePath);
121
+ try {
122
+ await fs.promises.access(filePath);
123
+ return true;
124
+ } catch {
125
+ return false;
126
+ }
127
+ }
128
+ async list({ location, prefix }) {
129
+ const base = getBasePath(location);
130
+ const dir = prefix ? safeResolve(base, prefix) : path$1.resolve(base);
131
+ try {
132
+ return (await fs.promises.readdir(dir, { withFileTypes: true })).map((e) => prefix ? `${prefix}/${e.name}` : e.name);
133
+ } catch {
134
+ return [];
135
+ }
136
+ }
137
+ async delete({ location, relativePath }) {
138
+ const filePath = safeResolve(getBasePath(location), relativePath);
139
+ await fs.promises.rm(filePath, { force: true });
140
+ }
141
+ async getAvailableSpace({ location }) {
142
+ const base = path$1.resolve(getBasePath(location));
143
+ try {
144
+ let target = base;
145
+ while (!fs.existsSync(target)) {
146
+ const parent = path$1.dirname(target);
147
+ if (!parent || parent === target) return null;
148
+ target = parent;
149
+ }
150
+ const stats = await fs.promises.statfs(target);
151
+ return stats.bavail * stats.bsize;
152
+ } catch {
153
+ return null;
154
+ }
155
+ }
156
+ async beginUpload({ location, relativePath }) {
157
+ const target = safeResolve(getBasePath(location), relativePath);
158
+ const partial = `${target}.partial`;
159
+ await fs.promises.mkdir(path$1.dirname(target), { recursive: true });
160
+ await fs.promises.unlink(partial).catch(() => {});
161
+ const stream = fs.createWriteStream(partial);
162
+ const uploadId = randomUUID();
163
+ const session = {
164
+ stream,
165
+ target,
166
+ partial,
167
+ bytesWritten: 0,
168
+ timer: setTimeout(() => {
169
+ this.abortUpload({ uploadId }).catch(() => {});
170
+ }, SESSION_IDLE_TTL_MS)
171
+ };
172
+ this.uploads.set(uploadId, session);
173
+ return { uploadId };
174
+ }
175
+ async writeChunk({ uploadId, offset, data }) {
176
+ const session = this.uploads.get(uploadId);
177
+ if (!session) throw new Error(`filesystem-storage: upload session "${uploadId}" not found`);
178
+ if (offset !== session.bytesWritten) throw new Error(`filesystem-storage: chunk offset mismatch (expected ${session.bytesWritten}, got ${offset})`);
179
+ await new Promise((resolve, reject) => {
180
+ session.stream.write(data, (err) => {
181
+ if (err) reject(err);
182
+ else resolve();
183
+ });
184
+ });
185
+ clearTimeout(session.timer);
186
+ const nextBytesWritten = session.bytesWritten + data.length;
187
+ this.uploads.set(uploadId, {
188
+ ...session,
189
+ bytesWritten: nextBytesWritten,
190
+ timer: setTimeout(() => {
191
+ this.abortUpload({ uploadId }).catch(() => {});
192
+ }, SESSION_IDLE_TTL_MS)
193
+ });
194
+ }
195
+ async finalizeUpload({ uploadId }) {
196
+ const session = this.uploads.get(uploadId);
197
+ if (!session) throw new Error(`filesystem-storage: upload session "${uploadId}" not found`);
198
+ await new Promise((resolve, reject) => {
199
+ session.stream.end(() => resolve());
200
+ session.stream.on("error", reject);
201
+ });
202
+ await fs.promises.rename(session.partial, session.target);
203
+ clearTimeout(session.timer);
204
+ this.uploads.delete(uploadId);
205
+ }
206
+ async abortUpload({ uploadId }) {
207
+ const session = this.uploads.get(uploadId);
208
+ if (!session) return;
209
+ try {
210
+ session.stream.destroy();
211
+ } catch {}
212
+ await fs.promises.unlink(session.partial).catch(() => {});
213
+ clearTimeout(session.timer);
214
+ this.uploads.delete(uploadId);
215
+ }
216
+ async beginDownload({ location, relativePath }) {
217
+ const target = safeResolve(getBasePath(location), relativePath);
218
+ const stat = await fs.promises.stat(target);
219
+ const fd = await fs.promises.open(target, "r");
220
+ const downloadId = randomUUID();
221
+ const session = {
222
+ fd,
223
+ sizeBytes: stat.size,
224
+ timer: setTimeout(() => {
225
+ this.endDownload({ downloadId }).catch(() => {});
226
+ }, SESSION_IDLE_TTL_MS)
227
+ };
228
+ this.downloads.set(downloadId, session);
229
+ return {
230
+ downloadId,
231
+ sizeBytes: stat.size
232
+ };
233
+ }
234
+ async readChunk({ downloadId, offset, length }) {
235
+ const session = this.downloads.get(downloadId);
236
+ if (!session) throw new Error(`filesystem-storage: download session "${downloadId}" not found`);
237
+ const out = new Uint8Array(new ArrayBuffer(length));
238
+ const { bytesRead } = await session.fd.read(out, 0, length, offset);
239
+ clearTimeout(session.timer);
240
+ this.downloads.set(downloadId, {
241
+ ...session,
242
+ timer: setTimeout(() => {
243
+ this.endDownload({ downloadId }).catch(() => {});
244
+ }, SESSION_IDLE_TTL_MS)
245
+ });
246
+ return out.subarray(0, bytesRead);
247
+ }
248
+ async endDownload({ downloadId }) {
249
+ const session = this.downloads.get(downloadId);
250
+ if (!session) return;
251
+ try {
252
+ await session.fd.close();
253
+ } catch {}
254
+ clearTimeout(session.timer);
255
+ this.downloads.delete(downloadId);
256
+ }
257
+ /**
258
+ * Abort all outstanding sessions. Called on addon shutdown so timers
259
+ * don't leak across hot-reload cycles.
260
+ */
261
+ async dispose() {
262
+ const uploadIds = [...this.uploads.keys()];
263
+ const downloadIds = [...this.downloads.keys()];
264
+ await Promise.all(uploadIds.map((uploadId) => this.abortUpload({ uploadId })));
265
+ await Promise.all(downloadIds.map((downloadId) => this.endDownload({ downloadId })));
266
+ }
267
+ };
268
+ //#endregion
269
+ //#region src/builtins/sqlite-storage/path-guard.ts
270
+ /**
271
+ * Allowed-root containment guard for the filesystem-browse cap. Pure +
272
+ * synchronous so it is trivially unit-testable; the provider applies it AFTER
273
+ * resolving the real path (so symlink escapes are also caught upstream).
274
+ */
275
+ /** True iff `candidate` is one of, or nested under, an allowed root. */
276
+ function isWithinAllowedRoots(candidate, allowedRoots) {
277
+ const norm = resolve(candidate);
278
+ return allowedRoots.some((root) => {
279
+ const r = resolve(root);
280
+ return norm === r || norm.startsWith(r.endsWith(sep) ? r : r + sep);
281
+ });
282
+ }
283
+ /** Throws unless `candidate` is within an allowed root. */
284
+ function assertWithinAllowedRoots(candidate, allowedRoots) {
285
+ if (!isWithinAllowedRoots(candidate, allowedRoots)) throw new Error(`path "${candidate}" is not within an allowed root`);
286
+ }
287
+ //#endregion
288
+ //#region src/builtins/sqlite-storage/filesystem-browse-provider.ts
289
+ /**
290
+ * Per-node filesystem-browse provider. Lists/creates directories under
291
+ * operator-configured allowed roots only (sandboxed by `path-guard`). Backs the
292
+ * `filesystem-browse` cap so the admin UI can pick a node + path for a
293
+ * node-local storage location.
294
+ */
295
+ var FilesystemBrowseProvider = class {
296
+ allowedRoots;
297
+ /** allowedRoots is injected (read from addon config) so it stays per-node + testable. */
298
+ constructor(allowedRoots) {
299
+ this.allowedRoots = allowedRoots;
300
+ }
301
+ async listAllowedRoots() {
302
+ return [...this.allowedRoots()];
303
+ }
304
+ async browse({ path }) {
305
+ const real = await realpath(path).catch(() => path);
306
+ assertWithinAllowedRoots(real, await this.resolvedRoots());
307
+ const entries = (await readdir(real, { withFileTypes: true })).filter((d) => d.isDirectory()).map((d) => ({
308
+ name: d.name,
309
+ path: join(real, d.name)
310
+ }));
311
+ const fs = await statfs(real);
312
+ const totalBytes = fs.blocks * fs.bsize;
313
+ return {
314
+ path: real,
315
+ entries,
316
+ freeBytes: fs.bavail * fs.bsize,
317
+ totalBytes
318
+ };
319
+ }
320
+ async createDir({ path }) {
321
+ const parent = dirname(path);
322
+ assertWithinAllowedRoots(join(await realpath(parent).catch(() => parent), basename(path)), await this.resolvedRoots());
323
+ await mkdir(path, { recursive: true });
324
+ return { path };
325
+ }
326
+ /** Allowed roots with symlinks resolved (falls back to the raw root if it does not exist). */
327
+ async resolvedRoots() {
328
+ return Promise.all(this.allowedRoots().map((r) => realpath(r).catch(() => r)));
329
+ }
330
+ };
331
+ //#endregion
332
+ //#region src/builtins/sqlite-storage/filesystem-storage.addon.ts
333
+ function defaultAllowedRoots() {
334
+ return [
335
+ "/mnt",
336
+ "/Volumes",
337
+ "/media",
338
+ homedir()
339
+ ];
340
+ }
341
+ /**
342
+ * Filesystem Storage addon — registers a filesystem-backed
343
+ * `storage-provider` (Task 7).
344
+ *
345
+ * Pre-Task 7 this addon registered against the (then-collection)
346
+ * `storage` cap. After Tasks 2/3/4 the consumer-facing `storage` cap is
347
+ * a singleton owned by `storage-orchestrator`; the orchestrator
348
+ * dispatches each call to whichever `storage-provider` matches
349
+ * `location.providerId`.
350
+ *
351
+ * The OLD `rootPath` config field is preserved for back-compat: the
352
+ * orchestrator's first-boot seeding step still uses the same env-var
353
+ * resolution chain (operator override → `CAMSTACK_DATA` → fallback) to
354
+ * derive the base path of every default location, then writes
355
+ * `config.basePath` into each persisted `StorageLocation` row. The
356
+ * provider itself is now stateless — every call carries the live
357
+ * `StorageLocation` inline.
358
+ */
359
+ var FilesystemStorageAddon = class extends BaseAddon {
360
+ provider = null;
361
+ constructor() {
362
+ super({
363
+ rootPath: "camstack-data",
364
+ storageAllowedRoots: defaultAllowedRoots()
365
+ });
366
+ }
367
+ async onInitialize() {
368
+ this.provider = new FilesystemStorageProvider();
369
+ const browse = new FilesystemBrowseProvider(() => this.config.storageAllowedRoots);
370
+ this.ctx.logger.info("Filesystem storage-provider initialized");
371
+ return [{
372
+ capability: storageProviderCapability,
373
+ provider: this.provider
374
+ }, {
375
+ capability: filesystemBrowseCapability,
376
+ provider: browse
377
+ }];
378
+ }
379
+ async onShutdown() {
380
+ await this.provider?.dispose();
381
+ this.provider = null;
382
+ }
383
+ getProvider() {
384
+ return this.provider;
385
+ }
386
+ };
387
+ //#endregion
388
+ export { FilesystemStorageAddon, FilesystemStorageAddon as default, FilesystemStorageProvider as t };
@@ -0,0 +1,8 @@
1
+ export { FilesystemStorageProvider } from './filesystem-storage-provider.js';
2
+ export { FilesystemStorageAddon } from './filesystem-storage.addon.js';
3
+ export { SqliteSettingsBackend } from './sqlite-settings-backend.js';
4
+ export { SqliteSettingsAddon } from './sqlite-settings.addon.js';
5
+ export { DeviceStore } from './device-store.js';
6
+ export type { DeviceRow } from './device-store.js';
7
+ export { ConfigStore } from './config-store.js';
8
+ export { FilesystemStorageAddon as default } from './filesystem-storage.addon.js';
@@ -0,0 +1,62 @@
1
+ Object.defineProperties(exports, {
2
+ __esModule: { value: true },
3
+ [Symbol.toStringTag]: { value: "Module" }
4
+ });
5
+ require("../../chunk-Cek0wNdY.js");
6
+ const require_builtins_sqlite_storage_filesystem_storage_addon = require("./filesystem-storage.addon.js");
7
+ const require_builtins_sqlite_storage_sqlite_settings_addon = require("./sqlite-settings.addon.js");
8
+ //#region src/builtins/sqlite-storage/device-store.ts
9
+ var DeviceStore = class {
10
+ db;
11
+ constructor(db) {
12
+ this.db = db;
13
+ }
14
+ insert(addonId, device) {
15
+ this.db.prepare(`INSERT INTO devices (addon_id, stable_id, type, name, parent_stable_id) VALUES (?, ?, ?, ?, ?)`).run(addonId, device.stableId, device.type, device.name, device.parentStableId);
16
+ }
17
+ listByAddon(addonId) {
18
+ return this.db.prepare(`SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ?`).all(addonId);
19
+ }
20
+ listChildren(addonId, parentStableId) {
21
+ return this.db.prepare(`SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ? AND parent_stable_id = ?`).all(addonId, parentStableId);
22
+ }
23
+ remove(addonId, stableId) {
24
+ this.db.prepare(`DELETE FROM devices WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId);
25
+ }
26
+ };
27
+ //#endregion
28
+ //#region src/builtins/sqlite-storage/config-store.ts
29
+ var ConfigStore = class {
30
+ db;
31
+ constructor(db) {
32
+ this.db = db;
33
+ }
34
+ save(addonId, stableId, data) {
35
+ if (stableId === null) this.db.prepare(`INSERT INTO addon_config (addon_id, stable_id, data, updated_at)
36
+ VALUES (?, NULL, ?, datetime('now'))
37
+ ON CONFLICT(addon_id) WHERE stable_id IS NULL
38
+ DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`).run(addonId, JSON.stringify(data));
39
+ else this.db.prepare(`INSERT INTO addon_config (addon_id, stable_id, data, updated_at)
40
+ VALUES (?, ?, ?, datetime('now'))
41
+ ON CONFLICT(addon_id, stable_id) WHERE stable_id IS NOT NULL
42
+ DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`).run(addonId, stableId, JSON.stringify(data));
43
+ }
44
+ load(addonId, stableId) {
45
+ const row = stableId === null ? this.db.prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`).get(addonId) : this.db.prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id = ?`).get(addonId, stableId);
46
+ return row ? JSON.parse(row.data) : {};
47
+ }
48
+ remove(addonId, stableId) {
49
+ if (stableId === null) this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`).run(addonId);
50
+ else this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId);
51
+ }
52
+ };
53
+ //#endregion
54
+ exports.ConfigStore = ConfigStore;
55
+ exports.ConfigStore$1 = ConfigStore;
56
+ exports.DeviceStore = DeviceStore;
57
+ exports.DeviceStore$1 = DeviceStore;
58
+ exports.FilesystemStorageAddon = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageAddon;
59
+ exports.FilesystemStorageProvider = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageProvider;
60
+ exports.SqliteSettingsAddon = require_builtins_sqlite_storage_sqlite_settings_addon.SqliteSettingsAddon;
61
+ exports.SqliteSettingsBackend = require_builtins_sqlite_storage_sqlite_settings_addon.SqliteSettingsBackend;
62
+ exports.default = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageAddon;
@@ -0,0 +1,49 @@
1
+ import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./filesystem-storage.addon.mjs";
2
+ import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./sqlite-settings.addon.mjs";
3
+ //#region src/builtins/sqlite-storage/device-store.ts
4
+ var DeviceStore = class {
5
+ db;
6
+ constructor(db) {
7
+ this.db = db;
8
+ }
9
+ insert(addonId, device) {
10
+ this.db.prepare(`INSERT INTO devices (addon_id, stable_id, type, name, parent_stable_id) VALUES (?, ?, ?, ?, ?)`).run(addonId, device.stableId, device.type, device.name, device.parentStableId);
11
+ }
12
+ listByAddon(addonId) {
13
+ return this.db.prepare(`SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ?`).all(addonId);
14
+ }
15
+ listChildren(addonId, parentStableId) {
16
+ return this.db.prepare(`SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ? AND parent_stable_id = ?`).all(addonId, parentStableId);
17
+ }
18
+ remove(addonId, stableId) {
19
+ this.db.prepare(`DELETE FROM devices WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId);
20
+ }
21
+ };
22
+ //#endregion
23
+ //#region src/builtins/sqlite-storage/config-store.ts
24
+ var ConfigStore = class {
25
+ db;
26
+ constructor(db) {
27
+ this.db = db;
28
+ }
29
+ save(addonId, stableId, data) {
30
+ if (stableId === null) this.db.prepare(`INSERT INTO addon_config (addon_id, stable_id, data, updated_at)
31
+ VALUES (?, NULL, ?, datetime('now'))
32
+ ON CONFLICT(addon_id) WHERE stable_id IS NULL
33
+ DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`).run(addonId, JSON.stringify(data));
34
+ else this.db.prepare(`INSERT INTO addon_config (addon_id, stable_id, data, updated_at)
35
+ VALUES (?, ?, ?, datetime('now'))
36
+ ON CONFLICT(addon_id, stable_id) WHERE stable_id IS NOT NULL
37
+ DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`).run(addonId, stableId, JSON.stringify(data));
38
+ }
39
+ load(addonId, stableId) {
40
+ const row = stableId === null ? this.db.prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`).get(addonId) : this.db.prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id = ?`).get(addonId, stableId);
41
+ return row ? JSON.parse(row.data) : {};
42
+ }
43
+ remove(addonId, stableId) {
44
+ if (stableId === null) this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`).run(addonId);
45
+ else this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId);
46
+ }
47
+ };
48
+ //#endregion
49
+ export { ConfigStore, DeviceStore, FilesystemStorageAddon, FilesystemStorageAddon as default, FilesystemStorageProvider, SqliteSettingsAddon, SqliteSettingsBackend };
@@ -0,0 +1,27 @@
1
+ import { Integration, PersistedDevice, CreateIntegrationInput, CreateDeviceInput, IIntegrationRegistry, ISettingsBackend } from '@camstack/types';
2
+ export declare class IntegrationRegistry implements IIntegrationRegistry {
3
+ private readonly backend;
4
+ constructor(backend: ISettingsBackend);
5
+ initialize(): Promise<void>;
6
+ createIntegration(input: CreateIntegrationInput): Promise<Integration>;
7
+ getIntegration(id: string): Promise<Integration | null>;
8
+ getIntegrationByAddonId(addonId: string): Promise<Integration | null>;
9
+ listIntegrations(): Promise<readonly Integration[]>;
10
+ updateIntegration(id: string, updates: Partial<Pick<Integration, 'name' | 'enabled' | 'info'>>): Promise<Integration | null>;
11
+ deleteIntegration(id: string): Promise<boolean>;
12
+ getIntegrationSettings(integrationId: string): Promise<Record<string, unknown>>;
13
+ setIntegrationSetting(integrationId: string, key: string, value: unknown): Promise<void>;
14
+ setIntegrationSettings(integrationId: string, settings: Record<string, unknown>): Promise<void>;
15
+ createDevice(input: CreateDeviceInput): Promise<PersistedDevice>;
16
+ getDevice(id: string): Promise<PersistedDevice | null>;
17
+ getDeviceByStableId(stableId: string): Promise<PersistedDevice | null>;
18
+ listDevices(integrationId?: string): Promise<readonly PersistedDevice[]>;
19
+ listCameras(): Promise<readonly PersistedDevice[]>;
20
+ updateDevice(id: string, updates: Partial<Pick<PersistedDevice, 'name' | 'enabled' | 'info'>>): Promise<PersistedDevice | null>;
21
+ deleteDevice(id: string): Promise<boolean>;
22
+ getDeviceSettings(deviceId: string): Promise<Record<string, unknown>>;
23
+ setDeviceSetting(deviceId: string, key: string, value: unknown): Promise<void>;
24
+ setDeviceSettings(deviceId: string, settings: Record<string, unknown>): Promise<void>;
25
+ private mapIntegration;
26
+ private mapDevice;
27
+ }
@@ -0,0 +1,4 @@
1
+ /** True iff `candidate` is one of, or nested under, an allowed root. */
2
+ export declare function isWithinAllowedRoots(candidate: string, allowedRoots: readonly string[]): boolean;
3
+ /** Throws unless `candidate` is within an allowed root. */
4
+ export declare function assertWithinAllowedRoots(candidate: string, allowedRoots: readonly string[]): void;
@@ -0,0 +1,102 @@
1
+ import { default as Database } from 'better-sqlite3';
2
+ import { ISettingsBackend, SettingsRecord, TableSchema, TableQueryOptions, SettingsGetInput, SettingsSetInput, SettingsQueryInput, SettingsInsertInput, SettingsUpdateInput, SettingsDeleteInput, SettingsCountInput, SettingsIsEmptyInput, SettingsHistogramInput, HistogramBucket, CollectionColumn, CollectionIndex } from '@camstack/types';
3
+ /**
4
+ * SQLite implementation of ISettingsBackend.
5
+ *
6
+ * Every collection is structured: declared at boot (canonical KV
7
+ * collections) or via `declareCollection` (typed schemas like
8
+ * `pipeline-analytics:tracks`). The legacy auto-create-on-first-access
9
+ * path was removed — see commit history for the migration. Operations
10
+ * on undeclared collections throw at runtime so callers fail fast.
11
+ *
12
+ * WAL mode for concurrent reads.
13
+ */
14
+ export declare class SqliteSettingsBackend implements ISettingsBackend {
15
+ private readonly dbPath;
16
+ private db;
17
+ private readonly structuredTables;
18
+ /** Map from scoped collection name → set of column names (non-id) that
19
+ * the structured schema owns. Routes set/get/insert/update/query to
20
+ * typed columns. Every collection MUST be declared here before use. */
21
+ private readonly declaredCollections;
22
+ private readonly runtimeDefaults;
23
+ /**
24
+ * Canonical key/value collections — declared with a `(id TEXT PK,
25
+ * data TEXT NOT NULL)` schema at boot so existing JSON-blob rows
26
+ * keep working through the structured path. Generates SQL identical
27
+ * to the previous legacy path; only the routing is unified.
28
+ */
29
+ private static readonly CANONICAL_KV_COLLECTIONS;
30
+ constructor(dbPath: string, runtimeDefaults?: Record<string, unknown>);
31
+ initialize(): Promise<void>;
32
+ private requireDeclared;
33
+ /**
34
+ * Decode a single column value off a SQLite row into the JS shape the
35
+ * caller's schema expects. Handles:
36
+ * - BOOLEAN columns: `0|1` → `false|true`
37
+ * - JSON columns (TEXT starting with `{` / `[`): parse
38
+ * - everything else: pass-through with null coalescing
39
+ */
40
+ private decodeColumnValue;
41
+ shutdown(): Promise<void>;
42
+ get({ namespace, collection, key }: SettingsGetInput): Promise<unknown>;
43
+ set({ namespace, collection, key, value }: SettingsSetInput): Promise<void>;
44
+ query<T extends object = Record<string, unknown>>({ namespace, collection, filter, }: SettingsQueryInput): Promise<readonly SettingsRecord<T>[]>;
45
+ insert<T extends object = Record<string, unknown>>({ namespace, collection, record, }: SettingsInsertInput<T>): Promise<void>;
46
+ update({ namespace, collection, id, data }: SettingsUpdateInput): Promise<void>;
47
+ delete({ namespace, collection, key }: SettingsDeleteInput): Promise<void>;
48
+ count({ namespace, collection, filter }: SettingsCountInput): Promise<number>;
49
+ histogram({ namespace, collection, field, bucketSize, origin, filter, }: SettingsHistogramInput): Promise<readonly HistogramBucket[]>;
50
+ isEmpty({ namespace, collection }: SettingsIsEmptyInput): Promise<boolean>;
51
+ private queryDeclared;
52
+ /** Get a system setting by dot-notation key */
53
+ getSystem(key: string): unknown;
54
+ /** Set a system setting */
55
+ setSystem(key: string, value: unknown): void;
56
+ /** Get all system settings as flat key-value */
57
+ getAllSystem(): Record<string, unknown>;
58
+ /** Get all settings for an addon */
59
+ getAllAddon(addonId: string): Record<string, unknown>;
60
+ /** Bulk-set all settings for an addon */
61
+ setAllAddon(addonId: string, config: Record<string, unknown>): void;
62
+ /** Get all settings for a provider */
63
+ getAllProvider(providerId: string): Record<string, unknown>;
64
+ /** Set a provider setting */
65
+ setProvider(providerId: string, key: string, value: unknown): void;
66
+ /** Get all settings for a device */
67
+ getAllDevice(deviceId: string): Record<string, unknown>;
68
+ /** Set a device setting */
69
+ setDevice(deviceId: string, key: string, value: unknown): void;
70
+ getAddonDevice(addonId: string, deviceId: string): Record<string, unknown>;
71
+ setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void;
72
+ clearAddonDevice(addonId: string, deviceId: string): void;
73
+ /** Seed system-settings with runtime defaults (first boot) */
74
+ private seedDefaults;
75
+ /**
76
+ * Expose the raw better-sqlite3 Database instance for components that
77
+ * need direct SQL access (e.g. DeviceStore, ConfigStore).
78
+ * Returns null if the backend has not been initialized yet.
79
+ */
80
+ getDatabase(): Database.Database | null;
81
+ private getDb;
82
+ private getAllScoped;
83
+ private setScopedKey;
84
+ private scopedName;
85
+ declareCollection(input: {
86
+ namespace?: string;
87
+ collection: string;
88
+ columns: readonly CollectionColumn[];
89
+ indexes?: readonly CollectionIndex[];
90
+ }): Promise<void>;
91
+ /** Serialise per-column values for SQL binding: objects → JSON, booleans → 0/1. */
92
+ private serializeColumnValue;
93
+ ensureTable(table: string, schema: TableSchema): Promise<void>;
94
+ tableInsert(table: string, row: Record<string, unknown>): Promise<void>;
95
+ tableUpdate(table: string, filter: Record<string, unknown>, updates: Record<string, unknown>): Promise<number>;
96
+ tableDelete(table: string, filter: Record<string, unknown>): Promise<number>;
97
+ tableQuery(table: string, options?: TableQueryOptions): Promise<readonly Record<string, unknown>[]>;
98
+ tableGet(table: string, filter: Record<string, unknown>): Promise<Record<string, unknown> | null>;
99
+ tableCount(table: string, filter?: Record<string, unknown>): Promise<number>;
100
+ private buildWhere;
101
+ }
102
+ export default SqliteSettingsBackend;
@@ -0,0 +1,14 @@
1
+ import { ProviderRegistration, BaseAddon } from '@camstack/types';
2
+ import { SqliteSettingsBackend } from './sqlite-settings-backend.js';
3
+ /**
4
+ * SQLite Settings addon — provides persistent settings storage.
5
+ * Capability: 'settings-store' (singleton)
6
+ */
7
+ export declare class SqliteSettingsAddon extends BaseAddon {
8
+ private backend;
9
+ constructor();
10
+ protected onInitialize(): Promise<ProviderRegistration[]>;
11
+ protected onShutdown(): Promise<void>;
12
+ getBackend(): SqliteSettingsBackend | null;
13
+ }
14
+ export default SqliteSettingsAddon;