@camstack/core 0.1.15 → 0.1.16

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 (382) hide show
  1. package/dist/addon/addon-api-factory.d.ts +36 -0
  2. package/dist/addon/addon-api-factory.d.ts.map +1 -0
  3. package/dist/addon-routes/addon-route-registry.d.ts +38 -0
  4. package/dist/addon-routes/addon-route-registry.d.ts.map +1 -0
  5. package/dist/auth/api-key-manager.d.ts +27 -0
  6. package/dist/auth/api-key-manager.d.ts.map +1 -0
  7. package/dist/auth/auth-manager.d.ts +47 -0
  8. package/dist/auth/auth-manager.d.ts.map +1 -0
  9. package/dist/auth/parse-record.d.ts +19 -0
  10. package/dist/auth/parse-record.d.ts.map +1 -0
  11. package/dist/auth/scoped-token-manager.d.ts +18 -0
  12. package/dist/auth/scoped-token-manager.d.ts.map +1 -0
  13. package/dist/auth/user-manager.d.ts +34 -0
  14. package/dist/auth/user-manager.d.ts.map +1 -0
  15. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.d.ts +54 -0
  16. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.d.ts.map +1 -0
  17. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +223 -217
  18. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js.map +1 -1
  19. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +216 -7
  20. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs.map +1 -1
  21. package/dist/builtins/addon-pages-aggregator/index.d.ts +2 -0
  22. package/dist/builtins/addon-pages-aggregator/index.d.ts.map +1 -0
  23. package/dist/builtins/addon-pages-aggregator/index.js +6 -221
  24. package/dist/builtins/addon-pages-aggregator/index.mjs +2 -9
  25. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts +33 -0
  26. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.d.ts.map +1 -0
  27. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +199 -197
  28. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js.map +1 -1
  29. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +192 -7
  30. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs.map +1 -1
  31. package/dist/builtins/addon-widgets-aggregator/index.d.ts +2 -0
  32. package/dist/builtins/addon-widgets-aggregator/index.d.ts.map +1 -0
  33. package/dist/builtins/addon-widgets-aggregator/index.js +6 -201
  34. package/dist/builtins/addon-widgets-aggregator/index.mjs +2 -9
  35. package/dist/builtins/alerts/alerts.addon.d.ts +82 -0
  36. package/dist/builtins/alerts/alerts.addon.d.ts.map +1 -0
  37. package/dist/builtins/alerts/alerts.addon.js +590 -430
  38. package/dist/builtins/alerts/alerts.addon.js.map +1 -1
  39. package/dist/builtins/alerts/alerts.addon.mjs +595 -7
  40. package/dist/builtins/alerts/alerts.addon.mjs.map +1 -1
  41. package/dist/builtins/alerts/index.d.ts +2 -0
  42. package/dist/builtins/alerts/index.d.ts.map +1 -0
  43. package/dist/builtins/alerts/index.js +3 -443
  44. package/dist/builtins/alerts/index.mjs +2 -8
  45. package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.d.ts +8 -0
  46. package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.d.ts.map +1 -0
  47. package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.js +56 -0
  48. package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.js.map +1 -0
  49. package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.mjs +50 -0
  50. package/dist/builtins/auth-orchestrator/auth-orchestrator.addon.mjs.map +1 -0
  51. package/dist/builtins/auth-orchestrator/index.d.ts +2 -0
  52. package/dist/builtins/auth-orchestrator/index.d.ts.map +1 -0
  53. package/dist/builtins/auth-orchestrator/index.js +7 -0
  54. package/dist/builtins/auth-orchestrator/index.mjs +2 -0
  55. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts +148 -0
  56. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.d.ts.map +1 -0
  57. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +7639 -0
  58. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js.map +1 -0
  59. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +7627 -0
  60. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs.map +1 -0
  61. package/dist/builtins/backup-orchestrator/cron-helpers.d.ts +24 -0
  62. package/dist/builtins/backup-orchestrator/cron-helpers.d.ts.map +1 -0
  63. package/dist/builtins/backup-orchestrator/destination-policy.d.ts +73 -0
  64. package/dist/builtins/backup-orchestrator/destination-policy.d.ts.map +1 -0
  65. package/dist/builtins/backup-orchestrator/download-helpers.d.ts +13 -0
  66. package/dist/builtins/backup-orchestrator/download-helpers.d.ts.map +1 -0
  67. package/dist/builtins/backup-orchestrator/index.d.ts +3 -0
  68. package/dist/builtins/backup-orchestrator/index.d.ts.map +1 -0
  69. package/dist/builtins/backup-orchestrator/index.js +7 -0
  70. package/dist/builtins/backup-orchestrator/index.mjs +2 -0
  71. package/dist/builtins/backup-orchestrator/manifest-store.d.ts +78 -0
  72. package/dist/builtins/backup-orchestrator/manifest-store.d.ts.map +1 -0
  73. package/dist/builtins/console-logging/console-destination.d.ts +14 -0
  74. package/dist/builtins/console-logging/console-destination.d.ts.map +1 -0
  75. package/dist/builtins/console-logging/console-logging.addon.d.ts +26 -0
  76. package/dist/builtins/console-logging/console-logging.addon.d.ts.map +1 -0
  77. package/dist/builtins/console-logging/index.d.ts +4 -0
  78. package/dist/builtins/console-logging/index.d.ts.map +1 -0
  79. package/dist/builtins/console-logging/index.js +99 -235
  80. package/dist/builtins/console-logging/index.js.map +1 -1
  81. package/dist/builtins/console-logging/index.mjs +95 -9
  82. package/dist/builtins/console-logging/index.mjs.map +1 -1
  83. package/dist/builtins/device-manager/device-event-propagator.d.ts +27 -0
  84. package/dist/builtins/device-manager/device-event-propagator.d.ts.map +1 -0
  85. package/dist/builtins/device-manager/device-manager.addon.d.ts +259 -0
  86. package/dist/builtins/device-manager/device-manager.addon.d.ts.map +1 -0
  87. package/dist/builtins/device-manager/device-manager.addon.js +2125 -2127
  88. package/dist/builtins/device-manager/device-manager.addon.js.map +1 -1
  89. package/dist/builtins/device-manager/device-manager.addon.mjs +2145 -7
  90. package/dist/builtins/device-manager/device-manager.addon.mjs.map +1 -1
  91. package/dist/builtins/device-manager/index.d.ts +3 -0
  92. package/dist/builtins/device-manager/index.d.ts.map +1 -0
  93. package/dist/builtins/device-manager/index.js +6 -2156
  94. package/dist/builtins/device-manager/index.mjs +2 -10
  95. package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts +45 -0
  96. package/dist/builtins/hub-forwarder/hub-forwarder-destination.d.ts.map +1 -0
  97. package/dist/builtins/hub-forwarder/hub-forwarder.addon.d.ts +16 -0
  98. package/dist/builtins/hub-forwarder/hub-forwarder.addon.d.ts.map +1 -0
  99. package/dist/builtins/hub-forwarder/index.d.ts +4 -0
  100. package/dist/builtins/hub-forwarder/index.d.ts.map +1 -0
  101. package/dist/builtins/hub-forwarder/index.js +150 -291
  102. package/dist/builtins/hub-forwarder/index.js.map +1 -1
  103. package/dist/builtins/hub-forwarder/index.mjs +145 -9
  104. package/dist/builtins/hub-forwarder/index.mjs.map +1 -1
  105. package/dist/builtins/local-auth/auth-schema.d.ts +12 -0
  106. package/dist/builtins/local-auth/auth-schema.d.ts.map +1 -0
  107. package/dist/builtins/local-auth/index.d.ts +2 -0
  108. package/dist/builtins/local-auth/index.d.ts.map +1 -0
  109. package/dist/builtins/local-auth/index.js +3 -623
  110. package/dist/builtins/local-auth/index.mjs +2 -8
  111. package/dist/builtins/local-auth/local-auth.addon.d.ts +17 -0
  112. package/dist/builtins/local-auth/local-auth.addon.d.ts.map +1 -0
  113. package/dist/builtins/local-auth/local-auth.addon.js +6861 -589
  114. package/dist/builtins/local-auth/local-auth.addon.js.map +1 -1
  115. package/dist/builtins/local-auth/local-auth.addon.mjs +6883 -7
  116. package/dist/builtins/local-auth/local-auth.addon.mjs.map +1 -1
  117. package/dist/builtins/local-network/index.d.ts +3 -0
  118. package/dist/builtins/local-network/index.d.ts.map +1 -0
  119. package/dist/builtins/local-network/index.js +9 -0
  120. package/dist/builtins/local-network/index.mjs +2 -0
  121. package/dist/builtins/local-network/local-network.addon.d.ts +102 -0
  122. package/dist/builtins/local-network/local-network.addon.d.ts.map +1 -0
  123. package/dist/builtins/local-network/local-network.addon.js +404 -0
  124. package/dist/builtins/local-network/local-network.addon.js.map +1 -0
  125. package/dist/builtins/local-network/local-network.addon.mjs +392 -0
  126. package/dist/builtins/local-network/local-network.addon.mjs.map +1 -0
  127. package/dist/builtins/mesh-orchestrator/index.d.ts +2 -0
  128. package/dist/builtins/mesh-orchestrator/index.d.ts.map +1 -0
  129. package/dist/builtins/mesh-orchestrator/index.js +7 -0
  130. package/dist/builtins/mesh-orchestrator/index.mjs +2 -0
  131. package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.d.ts +9 -0
  132. package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.d.ts.map +1 -0
  133. package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.js +83 -0
  134. package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.js.map +1 -0
  135. package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.mjs +77 -0
  136. package/dist/builtins/mesh-orchestrator/mesh-orchestrator.addon.mjs.map +1 -0
  137. package/dist/builtins/native-metrics/index.d.ts +3 -0
  138. package/dist/builtins/native-metrics/index.d.ts.map +1 -0
  139. package/dist/builtins/native-metrics/native-metrics-provider.d.ts +49 -0
  140. package/dist/builtins/native-metrics/native-metrics-provider.d.ts.map +1 -0
  141. package/dist/builtins/native-metrics/native-metrics.addon.d.ts +74 -0
  142. package/dist/builtins/native-metrics/native-metrics.addon.d.ts.map +1 -0
  143. package/dist/builtins/native-metrics/native-metrics.addon.js +887 -861
  144. package/dist/builtins/native-metrics/native-metrics.addon.js.map +1 -1
  145. package/dist/builtins/native-metrics/native-metrics.addon.mjs +914 -5
  146. package/dist/builtins/native-metrics/native-metrics.addon.mjs.map +1 -1
  147. package/dist/builtins/platform-probe/index.d.ts +12 -0
  148. package/dist/builtins/platform-probe/index.d.ts.map +1 -0
  149. package/dist/builtins/platform-probe/index.js +539 -0
  150. package/dist/builtins/platform-probe/index.js.map +1 -0
  151. package/dist/builtins/platform-probe/index.mjs +530 -0
  152. package/dist/builtins/platform-probe/index.mjs.map +1 -0
  153. package/dist/builtins/platform-probe/inference-config-resolver.d.ts +30 -0
  154. package/dist/builtins/platform-probe/inference-config-resolver.d.ts.map +1 -0
  155. package/dist/builtins/platform-probe/platform-scorer.d.ts +22 -0
  156. package/dist/builtins/platform-probe/platform-scorer.d.ts.map +1 -0
  157. package/dist/builtins/remote-access-orchestrator/index.d.ts +2 -0
  158. package/dist/builtins/remote-access-orchestrator/index.d.ts.map +1 -0
  159. package/dist/builtins/remote-access-orchestrator/index.js +7 -0
  160. package/dist/builtins/remote-access-orchestrator/index.mjs +2 -0
  161. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts +9 -0
  162. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.d.ts.map +1 -0
  163. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +72 -0
  164. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js.map +1 -0
  165. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +66 -0
  166. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs.map +1 -0
  167. package/dist/builtins/snapshot/index.d.ts +3 -0
  168. package/dist/builtins/snapshot/index.d.ts.map +1 -0
  169. package/dist/builtins/snapshot/index.js +481 -491
  170. package/dist/builtins/snapshot/index.js.map +1 -1
  171. package/dist/builtins/snapshot/index.mjs +475 -464
  172. package/dist/builtins/snapshot/index.mjs.map +1 -1
  173. package/dist/builtins/snapshot/snapshot.addon.d.ts +121 -0
  174. package/dist/builtins/snapshot/snapshot.addon.d.ts.map +1 -0
  175. package/dist/builtins/sqlite-storage/config-store.d.ts +9 -0
  176. package/dist/builtins/sqlite-storage/config-store.d.ts.map +1 -0
  177. package/dist/builtins/sqlite-storage/device-store.d.ts +24 -0
  178. package/dist/builtins/sqlite-storage/device-store.d.ts.map +1 -0
  179. package/dist/builtins/sqlite-storage/filesystem-storage-provider.d.ts +87 -0
  180. package/dist/builtins/sqlite-storage/filesystem-storage-provider.d.ts.map +1 -0
  181. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts +32 -0
  182. package/dist/builtins/sqlite-storage/filesystem-storage.addon.d.ts.map +1 -0
  183. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +312 -56
  184. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js.map +1 -1
  185. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +305 -7
  186. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs.map +1 -1
  187. package/dist/builtins/sqlite-storage/index.d.ts +12 -0
  188. package/dist/builtins/sqlite-storage/index.d.ts.map +1 -0
  189. package/dist/builtins/sqlite-storage/index.js +229 -1001
  190. package/dist/builtins/sqlite-storage/index.js.map +1 -1
  191. package/dist/builtins/sqlite-storage/index.mjs +268 -26
  192. package/dist/builtins/sqlite-storage/index.mjs.map +1 -1
  193. package/dist/builtins/sqlite-storage/integration-registry.d.ts +28 -0
  194. package/dist/builtins/sqlite-storage/integration-registry.d.ts.map +1 -0
  195. package/dist/builtins/sqlite-storage/settings-store.d.ts +40 -0
  196. package/dist/builtins/sqlite-storage/settings-store.d.ts.map +1 -0
  197. package/dist/builtins/sqlite-storage/sql-schema.d.ts +33 -0
  198. package/dist/builtins/sqlite-storage/sql-schema.d.ts.map +1 -0
  199. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +94 -0
  200. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts.map +1 -0
  201. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts +15 -0
  202. package/dist/builtins/sqlite-storage/sqlite-settings.addon.d.ts.map +1 -0
  203. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +586 -653
  204. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js.map +1 -1
  205. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +582 -7
  206. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs.map +1 -1
  207. package/dist/builtins/storage-orchestrator/index.d.ts +7 -0
  208. package/dist/builtins/storage-orchestrator/index.d.ts.map +1 -0
  209. package/dist/builtins/storage-orchestrator/index.js +9 -0
  210. package/dist/builtins/storage-orchestrator/index.mjs +2 -0
  211. package/dist/builtins/storage-orchestrator/location-store.d.ts +50 -0
  212. package/dist/builtins/storage-orchestrator/location-store.d.ts.map +1 -0
  213. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +60 -0
  214. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts.map +1 -0
  215. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +755 -0
  216. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js.map +1 -0
  217. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +746 -0
  218. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs.map +1 -0
  219. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts +121 -0
  220. package/dist/builtins/storage-orchestrator/storage-orchestrator.service.d.ts.map +1 -0
  221. package/dist/builtins/system-backup/system-backup.service.d.ts +138 -0
  222. package/dist/builtins/system-backup/system-backup.service.d.ts.map +1 -0
  223. package/dist/builtins/system-config/index.d.ts +2 -0
  224. package/dist/builtins/system-config/index.d.ts.map +1 -0
  225. package/dist/builtins/system-config/index.js +6 -188
  226. package/dist/builtins/system-config/index.mjs +2 -10
  227. package/dist/builtins/system-config/system-config.addon.d.ts +11 -0
  228. package/dist/builtins/system-config/system-config.addon.d.ts.map +1 -0
  229. package/dist/builtins/system-config/system-config.addon.js +227 -180
  230. package/dist/builtins/system-config/system-config.addon.js.map +1 -1
  231. package/dist/builtins/system-config/system-config.addon.mjs +226 -7
  232. package/dist/builtins/system-config/system-config.addon.mjs.map +1 -1
  233. package/dist/builtins/turn-orchestrator/index.d.ts +2 -0
  234. package/dist/builtins/turn-orchestrator/index.d.ts.map +1 -0
  235. package/dist/builtins/turn-orchestrator/index.js +7 -0
  236. package/dist/builtins/turn-orchestrator/index.mjs +2 -0
  237. package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.d.ts +10 -0
  238. package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.d.ts.map +1 -0
  239. package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.js +78 -0
  240. package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.js.map +1 -0
  241. package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.mjs +72 -0
  242. package/dist/builtins/turn-orchestrator/turn-orchestrator.addon.mjs.map +1 -0
  243. package/dist/builtins/winston-logging/index.d.ts +4 -0
  244. package/dist/builtins/winston-logging/index.d.ts.map +1 -0
  245. package/dist/builtins/winston-logging/index.js +153 -300
  246. package/dist/builtins/winston-logging/index.js.map +1 -1
  247. package/dist/builtins/winston-logging/index.mjs +144 -9
  248. package/dist/builtins/winston-logging/index.mjs.map +1 -1
  249. package/dist/builtins/winston-logging/winston-destination.d.ts +22 -0
  250. package/dist/builtins/winston-logging/winston-destination.d.ts.map +1 -0
  251. package/dist/builtins/winston-logging/winston-logging.addon.d.ts +20 -0
  252. package/dist/builtins/winston-logging/winston-logging.addon.d.ts.map +1 -0
  253. package/dist/chunk-C13QxCFV.js +50 -0
  254. package/dist/chunk-hT5z_Zn9.mjs +35 -0
  255. package/dist/download/model-download-service.d.ts +42 -0
  256. package/dist/download/model-download-service.d.ts.map +1 -0
  257. package/dist/download/model-downloader.d.ts +32 -0
  258. package/dist/download/model-downloader.d.ts.map +1 -0
  259. package/dist/events/event-bus.d.ts +11 -0
  260. package/dist/events/event-bus.d.ts.map +1 -0
  261. package/dist/events/system-event-bus.d.ts +15 -0
  262. package/dist/events/system-event-bus.d.ts.map +1 -0
  263. package/dist/feature/feature-manager.d.ts +12 -0
  264. package/dist/feature/feature-manager.d.ts.map +1 -0
  265. package/dist/formatter-C-5An4Bl.mjs +164 -0
  266. package/dist/formatter-C-5An4Bl.mjs.map +1 -0
  267. package/dist/formatter-Dr_6NNZc.js +169 -0
  268. package/dist/formatter-Dr_6NNZc.js.map +1 -0
  269. package/dist/index.d.ts +76 -1696
  270. package/dist/index.d.ts.map +1 -0
  271. package/dist/index.js +7780 -8035
  272. package/dist/index.js.map +1 -1
  273. package/dist/index.mjs +7707 -2148
  274. package/dist/index.mjs.map +1 -1
  275. package/dist/lifecycle/lifecycle-state-machine.d.ts +29 -0
  276. package/dist/lifecycle/lifecycle-state-machine.d.ts.map +1 -0
  277. package/dist/logging/formatter.d.ts +31 -0
  278. package/dist/logging/formatter.d.ts.map +1 -0
  279. package/dist/logging/log-manager.d.ts +52 -0
  280. package/dist/logging/log-manager.d.ts.map +1 -0
  281. package/dist/logging/log-ring-buffer.d.ts +48 -0
  282. package/dist/logging/log-ring-buffer.d.ts.map +1 -0
  283. package/dist/logging/scoped-logger.d.ts +18 -0
  284. package/dist/logging/scoped-logger.d.ts.map +1 -0
  285. package/dist/network/network-quality.d.ts +12 -0
  286. package/dist/network/network-quality.d.ts.map +1 -0
  287. package/dist/notification/notification-service.d.ts +38 -0
  288. package/dist/notification/notification-service.d.ts.map +1 -0
  289. package/dist/notification/toast-service.d.ts +23 -0
  290. package/dist/notification/toast-service.d.ts.map +1 -0
  291. package/dist/pipeline/engine-manager-resolver.d.ts +16 -0
  292. package/dist/pipeline/engine-manager-resolver.d.ts.map +1 -0
  293. package/dist/pipeline/pipeline-runner.d.ts +9 -0
  294. package/dist/pipeline/pipeline-runner.d.ts.map +1 -0
  295. package/dist/pipeline/pipeline-validator.d.ts +14 -0
  296. package/dist/pipeline/pipeline-validator.d.ts.map +1 -0
  297. package/dist/process/resource-monitor.d.ts +12 -0
  298. package/dist/process/resource-monitor.d.ts.map +1 -0
  299. package/dist/python/python-env-manager.d.ts +13 -0
  300. package/dist/python/python-env-manager.d.ts.map +1 -0
  301. package/dist/repl/interfaces.d.ts +32 -0
  302. package/dist/repl/interfaces.d.ts.map +1 -0
  303. package/dist/repl/repl-engine.d.ts +9 -0
  304. package/dist/repl/repl-engine.d.ts.map +1 -0
  305. package/dist/resource-monitor-CmuWlmap.js +76 -0
  306. package/dist/resource-monitor-CmuWlmap.js.map +1 -0
  307. package/dist/resource-monitor-DcQdKGYU.mjs +59 -0
  308. package/dist/resource-monitor-DcQdKGYU.mjs.map +1 -0
  309. package/dist/storage/fs-storage-backend.d.ts +41 -0
  310. package/dist/storage/fs-storage-backend.d.ts.map +1 -0
  311. package/dist/storage/storage-location-manager.d.ts +24 -0
  312. package/dist/storage/storage-location-manager.d.ts.map +1 -0
  313. package/dist/storage/storage-manager.d.ts +77 -0
  314. package/dist/storage/storage-manager.d.ts.map +1 -0
  315. package/dist/tls/cert-manager.d.ts +27 -0
  316. package/dist/tls/cert-manager.d.ts.map +1 -0
  317. package/dist/tls/index.d.ts +2 -0
  318. package/dist/tls/index.d.ts.map +1 -0
  319. package/package.json +119 -23
  320. package/dist/builtins/addon-pages-aggregator/index.js.map +0 -1
  321. package/dist/builtins/addon-pages-aggregator/index.mjs.map +0 -1
  322. package/dist/builtins/addon-widgets-aggregator/index.js.map +0 -1
  323. package/dist/builtins/addon-widgets-aggregator/index.mjs.map +0 -1
  324. package/dist/builtins/alerts/index.js.map +0 -1
  325. package/dist/builtins/alerts/index.mjs.map +0 -1
  326. package/dist/builtins/device-manager/index.js.map +0 -1
  327. package/dist/builtins/device-manager/index.mjs.map +0 -1
  328. package/dist/builtins/local-auth/index.js.map +0 -1
  329. package/dist/builtins/local-auth/index.mjs.map +0 -1
  330. package/dist/builtins/local-backup/index.js +0 -173
  331. package/dist/builtins/local-backup/index.js.map +0 -1
  332. package/dist/builtins/local-backup/index.mjs +0 -10
  333. package/dist/builtins/local-backup/index.mjs.map +0 -1
  334. package/dist/builtins/system-config/index.js.map +0 -1
  335. package/dist/builtins/system-config/index.mjs.map +0 -1
  336. package/dist/chunk-2CIYKDRN.mjs +0 -1
  337. package/dist/chunk-2CIYKDRN.mjs.map +0 -1
  338. package/dist/chunk-2F76X6NL.mjs +0 -136
  339. package/dist/chunk-2F76X6NL.mjs.map +0 -1
  340. package/dist/chunk-2QUFBZ7M.mjs +0 -1
  341. package/dist/chunk-2QUFBZ7M.mjs.map +0 -1
  342. package/dist/chunk-3BK2Y7GY.mjs +0 -593
  343. package/dist/chunk-3BK2Y7GY.mjs.map +0 -1
  344. package/dist/chunk-4OOHFJHT.mjs +0 -421
  345. package/dist/chunk-4OOHFJHT.mjs.map +0 -1
  346. package/dist/chunk-4XHB7IHT.mjs +0 -809
  347. package/dist/chunk-4XHB7IHT.mjs.map +0 -1
  348. package/dist/chunk-6M2HSSTQ.mjs +0 -98
  349. package/dist/chunk-6M2HSSTQ.mjs.map +0 -1
  350. package/dist/chunk-7FI7SQS7.mjs +0 -135
  351. package/dist/chunk-7FI7SQS7.mjs.map +0 -1
  352. package/dist/chunk-ED57RCQE.mjs +0 -171
  353. package/dist/chunk-ED57RCQE.mjs.map +0 -1
  354. package/dist/chunk-FZN56HGQ.mjs +0 -626
  355. package/dist/chunk-FZN56HGQ.mjs.map +0 -1
  356. package/dist/chunk-GL4OOB25.mjs +0 -51
  357. package/dist/chunk-GL4OOB25.mjs.map +0 -1
  358. package/dist/chunk-KDG2NTDB.mjs +0 -137
  359. package/dist/chunk-KDG2NTDB.mjs.map +0 -1
  360. package/dist/chunk-NRBQWBDM.mjs +0 -191
  361. package/dist/chunk-NRBQWBDM.mjs.map +0 -1
  362. package/dist/chunk-O4V246GG.mjs +0 -2137
  363. package/dist/chunk-O4V246GG.mjs.map +0 -1
  364. package/dist/chunk-QT57H266.mjs +0 -163
  365. package/dist/chunk-QT57H266.mjs.map +0 -1
  366. package/dist/chunk-QX4RH25I.mjs +0 -141
  367. package/dist/chunk-QX4RH25I.mjs.map +0 -1
  368. package/dist/chunk-TB562PZX.mjs +0 -86
  369. package/dist/chunk-TB562PZX.mjs.map +0 -1
  370. package/dist/chunk-TDYPZXK5.mjs +0 -1
  371. package/dist/chunk-TDYPZXK5.mjs.map +0 -1
  372. package/dist/chunk-UJI4LN5P.mjs +0 -36
  373. package/dist/chunk-UJI4LN5P.mjs.map +0 -1
  374. package/dist/chunk-W6RTHQGP.mjs +0 -1
  375. package/dist/chunk-W6RTHQGP.mjs.map +0 -1
  376. package/dist/chunk-ZELBCPDC.mjs +0 -369
  377. package/dist/chunk-ZELBCPDC.mjs.map +0 -1
  378. package/dist/index.d.mts +0 -1696
  379. package/dist/resource-monitor-UZUGPIAU.mjs +0 -9
  380. package/dist/resource-monitor-UZUGPIAU.mjs.map +0 -1
  381. package/dist/storage-location-manager-HFNB3PCS.mjs +0 -7
  382. package/dist/storage-location-manager-HFNB3PCS.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/builtins/sqlite-storage/index.ts","../../../src/builtins/sqlite-storage/filesystem-storage.addon.ts","../../../src/builtins/sqlite-storage/sqlite-settings-backend.ts","../../../src/builtins/sqlite-storage/sqlite-settings.addon.ts","../../../src/builtins/sqlite-storage/settings-store.ts","../../../src/builtins/sqlite-storage/sql-schema.ts","../../../src/builtins/sqlite-storage/device-store.ts","../../../src/builtins/sqlite-storage/config-store.ts"],"sourcesContent":["// FilesystemStorageProvider lives in `@camstack/types/node` since 2026-05\n// (kernel ↔ core cycle break). Re-export for backward compat.\nexport { FilesystemStorageProvider } from '@camstack/types/node'\nexport { FilesystemStorageAddon } from './filesystem-storage.addon.js'\nexport { SqliteSettingsBackend } from './sqlite-settings-backend.js'\nexport { SqliteSettingsAddon } from './sqlite-settings.addon.js'\nexport { SettingsStore } from './settings-store.js'\nexport { CORE_TABLE_DDL, addonTableToDdl } from './sql-schema.js'\nexport type { AddonTableSchema } from './sql-schema.js'\nexport { DeviceStore } from './device-store.js'\nexport type { DeviceRow } from './device-store.js'\nexport { ConfigStore } from './config-store.js'\n\n// Default export for AddonLoader\nexport { FilesystemStorageAddon as default } from './filesystem-storage.addon.js'\n","import type {\n StorageLocationType,\n ProviderRegistration,\n} from '@camstack/types'\nimport { BaseAddon, storageCapability } from '@camstack/types'\nimport { FilesystemStorageProvider } from '@camstack/types/node'\n\ninterface FilesystemStorageConfig {\n readonly rootPath: string\n}\n\n/**\n * Filesystem Storage addon — provides local disk storage for all location types.\n * Capability: 'storage' (collection)\n */\nexport class FilesystemStorageAddon extends BaseAddon<FilesystemStorageConfig> {\n private provider: FilesystemStorageProvider | null = null\n\n constructor() {\n super({ rootPath: 'camstack-data' })\n }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const rootPath = (this.ctx.addonConfig as Record<string, unknown>).rootPath as string | undefined ?? this.config.rootPath\n\n const overrides: Partial<Record<StorageLocationType, string>> = {}\n const addonCfg = this.ctx.addonConfig as Record<string, unknown>\n for (const key of Object.keys(addonCfg)) {\n if (key.startsWith('override.')) {\n const loc = key.slice('override.'.length) as StorageLocationType\n overrides[loc] = addonCfg[key] as string\n }\n }\n\n this.provider = new FilesystemStorageProvider(rootPath, overrides)\n this.ctx.logger.info('Filesystem storage initialized', { meta: { rootPath: this.provider.getRootPath() } })\n return [{ capability: storageCapability, provider: this.provider }]\n }\n\n protected async onShutdown(): Promise<void> {\n this.provider = null\n }\n\n getProvider(): FilesystemStorageProvider | null {\n return this.provider\n }\n}\n\nexport default FilesystemStorageAddon\n","import Database from 'better-sqlite3'\nimport { randomUUID } from 'node:crypto'\nimport type {\n ISettingsBackend,\n SettingsRecord,\n TableSchema,\n TableQueryOptions,\n SettingsGetInput,\n SettingsSetInput,\n SettingsQueryInput,\n SettingsInsertInput,\n SettingsUpdateInput,\n SettingsDeleteInput,\n SettingsCountInput,\n SettingsIsEmptyInput,\n CollectionColumn,\n CollectionIndex,\n} from '@camstack/types'\nimport { asJsonObject, parseJsonUnknown } from '@camstack/types'\n\nfunction parseRowData(raw: string): Record<string, unknown> {\n return asJsonObject(parseJsonUnknown(raw)) ?? {}\n}\n\n/**\n * SQLite implementation of ISettingsBackend.\n *\n * Every collection is structured: declared at boot (canonical KV\n * collections) or via `declareCollection` (typed schemas like\n * `pipeline-analytics:tracks`). The legacy auto-create-on-first-access\n * path was removed — see commit history for the migration. Operations\n * on undeclared collections throw at runtime so callers fail fast.\n *\n * WAL mode for concurrent reads.\n */\nexport class SqliteSettingsBackend implements ISettingsBackend {\n private db: Database.Database | null = null\n private readonly structuredTables = new Set<string>()\n /** Map from scoped collection name → set of column names (non-id) that\n * the structured schema owns. Routes set/get/insert/update/query to\n * typed columns. Every collection MUST be declared here before use. */\n private readonly declaredCollections = new Map<string, { readonly primaryKey: string; readonly columns: ReadonlySet<string> }>()\n private readonly runtimeDefaults: Record<string, unknown>\n\n /**\n * Canonical key/value collections — declared with a `(id TEXT PK,\n * data TEXT NOT NULL)` schema at boot so existing JSON-blob rows\n * keep working through the structured path. Generates SQL identical\n * to the previous legacy path; only the routing is unified.\n */\n private static readonly CANONICAL_KV_COLLECTIONS: readonly string[] = [\n 'system-settings',\n 'addon-settings',\n 'addon-device-settings',\n 'addon-devices',\n 'device-runtime-state',\n 'sections',\n 'provider-settings',\n 'device-settings',\n 'alerts',\n ]\n\n // Domain schemas (auth, analytics events, …) live in the addons that\n // own them — same pattern as `pipeline-analytics` (`event-store.ts`,\n // `track-store.ts`, `media-store.ts`) and `local-auth` (`auth-schema.ts`).\n // The backend exposes `declareCollection` and stays out of domain\n // knowledge.\n\n constructor(private readonly dbPath: string, runtimeDefaults?: Record<string, unknown>) {\n this.runtimeDefaults = runtimeDefaults ?? {}\n }\n\n async initialize(): Promise<void> {\n // Ensure parent directory exists\n const dir = this.dbPath.substring(0, this.dbPath.lastIndexOf('/'))\n if (dir) {\n const fs = await import('node:fs')\n fs.mkdirSync(dir, { recursive: true })\n }\n this.db = new Database(this.dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n\n // Declare canonical KV collections so set/get/insert/update/query\n // reach the structured path uniformly. Schema is the same shape\n // legacy created on demand — `(id TEXT PK, data TEXT NOT NULL)` —\n // so existing rows continue to work with no migration.\n for (const collection of SqliteSettingsBackend.CANONICAL_KV_COLLECTIONS) {\n await this.ensureTable(collection, {\n columns: [\n { name: 'id', type: 'TEXT', primaryKey: true, notNull: true },\n { name: 'data', type: 'TEXT', notNull: true },\n ],\n })\n this.declaredCollections.set(collection, {\n primaryKey: 'id',\n columns: new Set(['data']),\n })\n }\n\n // Seed system-settings on first boot\n const isEmpty = await this.isEmpty({ collection: 'system-settings' })\n if (isEmpty) {\n await this.seedDefaults()\n }\n }\n\n private requireDeclared(scoped: string): { readonly primaryKey: string; readonly columns: ReadonlySet<string> } {\n const decl = this.declaredCollections.get(scoped)\n if (!decl) {\n throw new Error(`SqliteSettingsBackend: collection \"${scoped}\" is not declared. Call declareCollection() first or add it to CANONICAL_KV_COLLECTIONS.`)\n }\n return decl\n }\n\n async shutdown(): Promise<void> {\n this.db?.close()\n this.db = null\n }\n\n // ---------------------------------------------------------------------------\n // ISettingsBackend implementation\n // ---------------------------------------------------------------------------\n\n async get({ namespace, collection, key }: SettingsGetInput): Promise<unknown> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n // Canonical KV collections store the value as a single `data`\n // JSON-blob column; their declared columns are exactly `['data']`\n // so we read it back, parse, and return. Structured collections\n // (multi-column) return the row map untouched.\n const cols = [`\"${decl.primaryKey}\"`, ...[...decl.columns].map(c => `\"${c}\"`)].join(', ')\n const row = this.getDb()\n .prepare<[string], Record<string, unknown>>(`SELECT ${cols} FROM \"${scoped}\" WHERE \"${decl.primaryKey}\" = ?`)\n .get(key)\n if (!row) return undefined\n if (decl.columns.size === 1 && decl.columns.has('data')) {\n const raw = row['data']\n return typeof raw === 'string' ? JSON.parse(raw) : raw\n }\n const data: Record<string, unknown> = {}\n for (const c of decl.columns) {\n const v = row[c]\n if (typeof v === 'string' && (v.startsWith('{') || v.startsWith('['))) {\n try { data[c] = JSON.parse(v) } catch { data[c] = v }\n } else {\n data[c] = v ?? null\n }\n }\n return data\n }\n\n async set({ namespace, collection, key, value }: SettingsSetInput): Promise<void> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n // Build the column row. KV-shape collections (`['data']`) store\n // `value` as a JSON blob in `data`; structured collections\n // destructure `value` into its declared columns.\n const row: Record<string, unknown> = { [decl.primaryKey]: key }\n if (decl.columns.size === 1 && decl.columns.has('data')) {\n row['data'] = JSON.stringify(value)\n } else {\n const valueObj = (value !== null && typeof value === 'object' ? value : {}) as Record<string, unknown>\n for (const [k, v] of Object.entries(valueObj)) {\n if (decl.columns.has(k)) row[k] = this.serializeColumnValue(v)\n }\n }\n const keys = Object.keys(row)\n const cols = keys.map(k => `\"${k}\"`).join(', ')\n const placeholders = keys.map(() => '?').join(', ')\n const updates = keys\n .filter(k => k !== decl.primaryKey)\n .map(k => `\"${k}\" = excluded.\"${k}\"`)\n .join(', ')\n const values = keys.map(k => row[k])\n const sql = updates.length > 0\n ? `INSERT INTO \"${scoped}\" (${cols}) VALUES (${placeholders}) ON CONFLICT(\"${decl.primaryKey}\") DO UPDATE SET ${updates}`\n : `INSERT INTO \"${scoped}\" (${cols}) VALUES (${placeholders}) ON CONFLICT(\"${decl.primaryKey}\") DO NOTHING`\n this.getDb().prepare(sql).run(...values)\n }\n\n async query<T extends object = Record<string, unknown>>(\n { namespace, collection, filter }: SettingsQueryInput,\n ): Promise<readonly SettingsRecord<T>[]> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n return this.queryDeclared<T>(scoped, decl, filter)\n }\n\n async insert<T extends object = Record<string, unknown>>({ namespace, collection, record }: SettingsInsertInput<T>): Promise<void> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n const id = record.id || randomUUID()\n const row: Record<string, unknown> = { [decl.primaryKey]: id }\n if (decl.columns.size === 1 && decl.columns.has('data')) {\n row['data'] = JSON.stringify(record.data)\n } else {\n for (const [k, v] of Object.entries(record.data)) {\n if (decl.columns.has(k)) row[k] = this.serializeColumnValue(v)\n }\n }\n await this.tableInsert(scoped, row)\n }\n\n async update({ namespace, collection, id, data }: SettingsUpdateInput): Promise<void> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n const updates: Record<string, unknown> = {}\n if (decl.columns.size === 1 && decl.columns.has('data')) {\n updates['data'] = JSON.stringify(data)\n } else {\n for (const [k, v] of Object.entries(data)) {\n if (decl.columns.has(k)) updates[k] = this.serializeColumnValue(v)\n }\n }\n if (Object.keys(updates).length > 0) {\n await this.tableUpdate(scoped, { [decl.primaryKey]: id }, updates)\n }\n }\n\n async delete({ namespace, collection, key }: SettingsDeleteInput): Promise<void> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n await this.tableDelete(scoped, { [decl.primaryKey]: key })\n }\n\n async count({ namespace, collection, filter }: SettingsCountInput): Promise<number> {\n const scoped = this.scopedName(namespace, collection)\n const decl = this.requireDeclared(scoped)\n const isKvShape = decl.columns.size === 1 && decl.columns.has('data')\n const isColumn = (f: string): boolean => f === decl.primaryKey || decl.columns.has(f)\n const fieldExpr = (f: string): string => {\n if (isColumn(f)) return `\"${f}\"`\n if (isKvShape) return `json_extract(\"data\", '$.${f}')`\n return ''\n }\n let sql = `SELECT COUNT(*) AS cnt FROM \"${scoped}\"`\n const params: unknown[] = []\n if (filter?.where) {\n const clauses: string[] = []\n for (const [field, value] of Object.entries(filter.where)) {\n const expr = fieldExpr(field)\n if (!expr) continue\n clauses.push(`${expr} = ?`)\n params.push(this.serializeColumnValue(value))\n }\n if (clauses.length > 0) sql += ` WHERE ${clauses.join(' AND ')}`\n }\n const row = this.getDb()\n .prepare<unknown[], { cnt: number }>(sql)\n .get(...params)\n return row?.cnt ?? 0\n }\n\n async isEmpty({ namespace, collection }: SettingsIsEmptyInput): Promise<boolean> {\n const scoped = this.scopedName(namespace, collection)\n this.requireDeclared(scoped)\n return (await this.tableCount(scoped)) === 0\n }\n\n private async queryDeclared<T extends object = Record<string, unknown>>(\n table: string,\n decl: { readonly primaryKey: string; readonly columns: ReadonlySet<string> },\n filter: import('@camstack/types').QueryFilter | undefined,\n ): Promise<readonly SettingsRecord<T>[]> {\n // KV-shape detection: a single `data` JSON-blob column. Canonical\n // collections (users, alerts, api-keys, …) and the catalogue of\n // \"legacy\" collections fall in this branch — the structured per-\n // column path is reserved for explicitly declared schemas.\n const isKvShape = decl.columns.size === 1 && decl.columns.has('data')\n const cols = [`\"${decl.primaryKey}\"`, ...[...decl.columns].map(c => `\"${c}\"`)].join(', ')\n let sql = `SELECT ${cols} FROM \"${table}\"`\n const params: unknown[] = []\n const whereClauses: string[] = []\n\n const isColumn = (f: string): boolean => f === decl.primaryKey || decl.columns.has(f)\n /**\n * SQL expression for `field` on this table.\n * - real column → `\"field\"`\n * - KV blob field (e.g. `username` inside `data`) → `json_extract`\n * - structured table + non-column → empty string (filter dropped,\n * matches legacy structured-table behaviour)\n */\n const fieldExpr = (f: string): string => {\n if (isColumn(f)) return `\"${f}\"`\n if (isKvShape) return `json_extract(\"data\", '$.${f}')`\n return ''\n }\n\n if (filter?.where) {\n for (const [field, value] of Object.entries(filter.where)) {\n const expr = fieldExpr(field)\n if (!expr) continue\n whereClauses.push(`${expr} = ?`)\n params.push(this.serializeColumnValue(value))\n }\n }\n if (filter?.whereIn) {\n for (const [field, values] of Object.entries(filter.whereIn)) {\n const expr = fieldExpr(field)\n if (!expr) continue\n const placeholders = values.map(() => '?').join(', ')\n whereClauses.push(`${expr} IN (${placeholders})`)\n for (const v of values) params.push(this.serializeColumnValue(v))\n }\n }\n if (filter?.whereBetween) {\n for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {\n const expr = fieldExpr(field)\n if (!expr) continue\n whereClauses.push(`${expr} BETWEEN ? AND ?`)\n params.push(this.serializeColumnValue(low), this.serializeColumnValue(high))\n }\n }\n if (whereClauses.length > 0) sql += ` WHERE ${whereClauses.join(' AND ')}`\n\n if (filter?.orderBy) {\n const expr = fieldExpr(filter.orderBy.field)\n if (expr) {\n const dir = filter.orderBy.direction === 'desc' ? 'DESC' : 'ASC'\n sql += ` ORDER BY ${expr} ${dir}`\n }\n }\n if (filter?.limit !== undefined) { sql += ` LIMIT ?`; params.push(filter.limit) }\n if (filter?.offset !== undefined) { sql += ` OFFSET ?`; params.push(filter.offset) }\n\n const rows = this.getDb()\n .prepare<unknown[], Record<string, unknown>>(sql)\n .all(...params)\n return rows.map(r => {\n const id = String(r[decl.primaryKey] ?? '')\n // KV-shape: unwrap the JSON blob so consumers receive\n // `{ id, data: <parsedJSON> }` directly. Without this every caller\n // in the auth chain (UserManager, ScopedTokenManager, …) gets a\n // double-wrapped record (`{ data: { data: <real> } }`) and the\n // schema parse throws on undefined fields.\n if (isKvShape) {\n const v = r['data']\n if (typeof v === 'string' && (v.startsWith('{') || v.startsWith('['))) {\n try {\n return { id, data: JSON.parse(v) as T }\n } catch {\n return { id, data: { value: v } as unknown as T }\n }\n }\n return { id, data: (v == null ? {} : { value: v }) as T }\n }\n // Structured tables: reassemble the record from typed columns and\n // include the primary key field so callers (e.g. `UserRecordSchema.parse`)\n // see the same shape they originally inserted.\n const data: Record<string, unknown> = { [decl.primaryKey]: r[decl.primaryKey] ?? null }\n for (const c of decl.columns) {\n // Auto-deserialize JSON strings back into objects. Heuristic:\n // values starting with `{` or `[` are attempted as JSON.parse,\n // fallback to the raw string on parse failure. This preserves\n // primitive column types (TEXT/INTEGER/REAL) as-is.\n const v = r[c]\n if (typeof v === 'string' && (v.startsWith('{') || v.startsWith('['))) {\n try { data[c] = JSON.parse(v) } catch { data[c] = v }\n } else {\n data[c] = v ?? null\n }\n }\n return { id, data: data as T }\n })\n }\n\n // ---------------------------------------------------------------------------\n // Legacy SettingsStore compatibility (used by ConfigManager.setSettingsStore)\n // ---------------------------------------------------------------------------\n\n /** Get a system setting by dot-notation key */\n getSystem(key: string): unknown {\n this.requireDeclared('system-settings')\n const row = this.getDb()\n .prepare<[string], { data: string }>('SELECT data FROM \"system-settings\" WHERE id = ?')\n .get(key)\n if (!row) return undefined\n return JSON.parse(row.data)\n }\n\n /** Set a system setting */\n setSystem(key: string, value: unknown): void {\n this.requireDeclared('system-settings')\n this.getDb()\n .prepare('INSERT INTO \"system-settings\" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data')\n .run(key, JSON.stringify(value))\n }\n\n /** Get all system settings as flat key-value */\n getAllSystem(): Record<string, unknown> {\n this.requireDeclared('system-settings')\n const rows = this.getDb()\n .prepare<[], { id: string; data: string }>('SELECT id, data FROM \"system-settings\"')\n .all()\n return Object.fromEntries(rows.map(r => [r.id, JSON.parse(r.data)]))\n }\n\n /** Get all settings for an addon */\n getAllAddon(addonId: string): Record<string, unknown> {\n this.requireDeclared('addon-settings')\n const rows = this.getDb()\n .prepare<[string], { id: string; data: string }>('SELECT id, data FROM \"addon-settings\" WHERE json_extract(data, \\'$.addonId\\') = ?')\n .all(addonId)\n if (rows.length === 0) return {}\n // addon-settings stores all keys for an addon as separate rows with id\n // = \"addonId.key\" and data = `{ addonId, key, value }`. Detect the\n // wrapper shape explicitly: a corrupted legacy row that lacks the\n // `value` field would otherwise be returned as the wrapper itself\n // (the previous `parsed.value ?? parsed` fallback masked the bug as a\n // valid value, breaking downstream type-narrowed reads like\n // `pipelineDefaults`). Skip those rows entirely so the caller sees\n // them as missing.\n const result: Record<string, unknown> = {}\n for (const row of rows) {\n const parsed = parseRowData(row.data)\n const key = row.id.startsWith(`${addonId}.`) ? row.id.slice(addonId.length + 1) : row.id\n const isWrapper = parsed !== null\n && typeof parsed === 'object'\n && 'addonId' in parsed\n && 'key' in (parsed as Record<string, unknown>)\n if (isWrapper) {\n const wrapper = parsed as { readonly addonId: string; readonly key: string; readonly value?: unknown }\n if ('value' in wrapper) {\n result[key] = wrapper.value\n }\n // else: corrupted wrapper-without-value — skip\n } else {\n result[key] = parsed\n }\n }\n return result\n }\n\n /** Bulk-set all settings for an addon */\n setAllAddon(addonId: string, config: Record<string, unknown>): void {\n this.requireDeclared('addon-settings')\n const db = this.getDb()\n const deleteStmt = db.prepare('DELETE FROM \"addon-settings\" WHERE id LIKE ? || \\'%\\'')\n const insertStmt = db.prepare('INSERT INTO \"addon-settings\" (id, data) VALUES (?, ?)')\n db.transaction(() => {\n deleteStmt.run(`${addonId}.`)\n for (const [key, value] of Object.entries(config)) {\n // Skip undefined values: the JSON wrapper `{addonId, key, value}`\n // serializes as `{addonId, key}` when value is undefined, which\n // round-trips as a corrupted \"wrapper without value\" row.\n if (value === undefined) continue\n insertStmt.run(`${addonId}.${key}`, JSON.stringify({ addonId, key, value }))\n }\n })()\n }\n\n /** Get all settings for a provider */\n getAllProvider(providerId: string): Record<string, unknown> {\n return this.getAllScoped('provider-settings', providerId)\n }\n\n /** Set a provider setting */\n setProvider(providerId: string, key: string, value: unknown): void {\n this.setScopedKey('provider-settings', providerId, key, value)\n }\n\n /** Get all settings for a device */\n getAllDevice(deviceId: string): Record<string, unknown> {\n return this.getAllScoped('device-settings', deviceId)\n }\n\n /** Set a device setting */\n setDevice(deviceId: string, key: string, value: unknown): void {\n this.setScopedKey('device-settings', deviceId, key, value)\n }\n\n // ── Addon-device settings (per-device overrides of an addon's config) ──\n //\n // Storage key format: \"<addonId>:<deviceId>.<field>\" inside the\n // \"addon-device-settings\" collection. Uses the same JSON-blob layout\n // as the other scoped collections so no schema change is required.\n\n getAddonDevice(addonId: string, deviceId: string): Record<string, unknown> {\n return this.getAllScoped('addon-device-settings', `${addonId}:${deviceId}`)\n }\n\n setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void {\n this.requireDeclared('addon-device-settings')\n const db = this.getDb()\n const prefix = `${addonId}:${deviceId}.`\n const deleteStmt = db.prepare(`DELETE FROM \"addon-device-settings\" WHERE id LIKE ? || '%'`)\n const insertStmt = db.prepare(\n `INSERT INTO \"addon-device-settings\" (id, data) VALUES (?, ?)\n ON CONFLICT(id) DO UPDATE SET data = excluded.data`,\n )\n db.transaction(() => {\n deleteStmt.run(prefix)\n for (const [key, value] of Object.entries(values)) {\n insertStmt.run(\n `${prefix}${key}`,\n JSON.stringify({ addonId, deviceId, key, value }),\n )\n }\n })()\n }\n\n clearAddonDevice(addonId: string, deviceId: string): void {\n this.requireDeclared('addon-device-settings')\n const prefix = `${addonId}:${deviceId}.`\n this.getDb()\n .prepare(`DELETE FROM \"addon-device-settings\" WHERE id LIKE ? || '%'`)\n .run(prefix)\n }\n\n /** Seed system-settings with runtime defaults (first boot) */\n private async seedDefaults(): Promise<void> {\n this.requireDeclared('system-settings')\n const insert = this.getDb().prepare(\n 'INSERT OR IGNORE INTO \"system-settings\" (id, data) VALUES (?, ?)',\n )\n this.getDb().transaction(() => {\n for (const [key, value] of Object.entries(this.runtimeDefaults)) {\n insert.run(key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n /**\n * Expose the raw better-sqlite3 Database instance for components that\n * need direct SQL access (e.g. DeviceStore, ConfigStore).\n * Returns null if the backend has not been initialized yet.\n */\n getDatabase(): Database.Database | null {\n return this.db\n }\n\n private getDb(): Database.Database {\n if (!this.db) throw new Error('SqliteSettingsBackend not initialized — call initialize() first')\n return this.db\n }\n\n private getAllScoped(collection: string, scopeId: string): Record<string, unknown> {\n this.requireDeclared(collection)\n const rows = this.getDb()\n .prepare<[string], { id: string; data: string }>(`SELECT id, data FROM \"${collection}\" WHERE id LIKE ? || '.%'`)\n .all(scopeId)\n const result: Record<string, unknown> = {}\n for (const row of rows) {\n const key = row.id.slice(scopeId.length + 1)\n const parsed = parseRowData(row.data)\n result[key] = parsed.value ?? parsed\n }\n return result\n }\n\n private setScopedKey(collection: string, scopeId: string, key: string, value: unknown): void {\n this.requireDeclared(collection)\n this.getDb()\n .prepare(`INSERT INTO \"${collection}\" (id, data) VALUES (?, ?) ON CONFLICT(id) DO UPDATE SET data = excluded.data`)\n .run(`${scopeId}.${key}`, JSON.stringify({ scopeId, key, value }))\n }\n\n // ── Declared collections (typed SQL tables behind the generic cap) ──\n\n private scopedName(namespace: string | undefined, collection: string): string {\n return namespace ? `${namespace}:${collection}` : collection\n }\n\n async declareCollection(input: {\n namespace?: string\n collection: string\n columns: readonly CollectionColumn[]\n indexes?: readonly CollectionIndex[]\n }): Promise<void> {\n const table = this.scopedName(input.namespace, input.collection)\n if (this.declaredCollections.has(table)) return\n\n // Ensure id column is explicit — every declared collection needs a\n // primary key that maps to the cap's record.id.\n const hasId = input.columns.some(c => c.primaryKey === true)\n const columns = hasId\n ? input.columns\n : [{ name: 'id', type: 'TEXT' as const, primaryKey: true, notNull: true }, ...input.columns]\n\n // Translate to the TableSchema shape ensureTable expects.\n const schema: TableSchema = {\n columns: columns.map(c => ({\n name: c.name,\n // SQLite treats JSON as TEXT; we serialize object values on write\n // and parse on read.\n type: c.type === 'JSON' ? 'TEXT' : c.type,\n ...(c.primaryKey !== undefined ? { primaryKey: c.primaryKey } : {}),\n ...(c.notNull !== undefined ? { notNull: c.notNull } : {}),\n ...(c.unique !== undefined ? { unique: c.unique } : {}),\n })),\n ...(input.indexes ? { indexes: input.indexes.map(i => ({\n name: i.name,\n columns: i.columns,\n ...(i.unique !== undefined ? { unique: i.unique } : {}),\n })) } : {}),\n }\n await this.ensureTable(table, schema)\n\n const pkCol = columns.find(c => c.primaryKey === true)\n const primaryKey = pkCol ? pkCol.name : 'id'\n const columnNames = new Set(columns.filter(c => c.name !== primaryKey).map(c => c.name))\n this.declaredCollections.set(table, { primaryKey, columns: columnNames })\n }\n\n /** Serialise per-column values for SQL binding: objects → JSON, booleans → 0/1. */\n private serializeColumnValue(v: unknown): unknown {\n if (v === null || v === undefined) return v ?? null\n if (typeof v === 'boolean') return v ? 1 : 0\n if (typeof v === 'object') return JSON.stringify(v)\n return v\n }\n\n // ── Structured table operations ────────────────────────────────────\n\n async ensureTable(table: string, schema: TableSchema): Promise<void> {\n if (this.structuredTables.has(table)) return\n\n const colDefs = schema.columns.map(col => {\n const parts = [`\"${col.name}\" ${col.type}`]\n if (col.primaryKey) parts.push('PRIMARY KEY')\n if (col.notNull) parts.push('NOT NULL')\n if (col.unique) parts.push('UNIQUE')\n if (col.defaultValue !== undefined) {\n parts.push(`DEFAULT ${typeof col.defaultValue === 'string' ? `'${col.defaultValue}'` : col.defaultValue === null ? 'NULL' : String(col.defaultValue)}`)\n }\n return parts.join(' ')\n })\n\n // Drop a pre-existing legacy `(id, data)` shape when the caller\n // is now declaring a structured schema. Without this, an earlier\n // `ensureTable(table)` call (no schema → legacy KV table) wins\n // because `CREATE TABLE IF NOT EXISTS` is a no-op when the table\n // is already there with a different shape, and every subsequent\n // typed query throws \"no such column: deviceId\" / \"...id\".\n // Destructive — the caller (analytics tracks/events/media) is the\n // only consumer of the table, so the operator only sees a one-time\n // wipe of yet-unmigrated observability rings.\n type ColInfoRow = { name: string }\n const existingCols = this.getDb()\n .prepare(`PRAGMA table_info(\"${table}\")`)\n .all() as ColInfoRow[]\n if (existingCols.length > 0) {\n const existingNames = new Set(existingCols.map(c => c.name))\n const declaredNames = new Set(schema.columns.map(c => c.name))\n const missingDeclared = schema.columns.some(c => !existingNames.has(c.name))\n const isLegacyKv =\n existingCols.length <= 2 &&\n existingNames.has('data') &&\n !declaredNames.has('data')\n if (isLegacyKv || missingDeclared) {\n try { this.getDb().exec(`DROP TABLE \"${table}\"`) } catch { /* best-effort */ }\n }\n }\n\n this.getDb().exec(`CREATE TABLE IF NOT EXISTS \"${table}\" (${colDefs.join(', ')})`)\n\n if (schema.indexes) {\n for (const idx of schema.indexes) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n const cols = idx.columns.map(c => `\"${c}\"`).join(', ')\n this.getDb().exec(`CREATE ${unique}INDEX IF NOT EXISTS \"${idx.name}\" ON \"${table}\" (${cols})`)\n }\n }\n\n this.structuredTables.add(table)\n }\n\n async tableInsert(table: string, row: Record<string, unknown>): Promise<void> {\n const keys = Object.keys(row)\n const cols = keys.map(k => `\"${k}\"`).join(', ')\n const placeholders = keys.map(() => '?').join(', ')\n const values = keys.map(k => {\n const v = row[k]\n return typeof v === 'object' && v !== null ? JSON.stringify(v) : v\n })\n this.getDb().prepare(`INSERT INTO \"${table}\" (${cols}) VALUES (${placeholders})`).run(...values)\n }\n\n async tableUpdate(table: string, filter: Record<string, unknown>, updates: Record<string, unknown>): Promise<number> {\n const setClauses: string[] = []\n const setValues: unknown[] = []\n for (const [k, v] of Object.entries(updates)) {\n setClauses.push(`\"${k}\" = ?`)\n setValues.push(typeof v === 'object' && v !== null ? JSON.stringify(v) : v)\n }\n\n const { whereSql, whereValues } = this.buildWhere(filter)\n const result = this.getDb()\n .prepare(`UPDATE \"${table}\" SET ${setClauses.join(', ')}${whereSql}`)\n .run(...setValues, ...whereValues)\n return result.changes\n }\n\n async tableDelete(table: string, filter: Record<string, unknown>): Promise<number> {\n const { whereSql, whereValues } = this.buildWhere(filter)\n const result = this.getDb().prepare(`DELETE FROM \"${table}\"${whereSql}`).run(...whereValues)\n return result.changes\n }\n\n async tableQuery(table: string, options?: TableQueryOptions): Promise<readonly Record<string, unknown>[]> {\n let sql = `SELECT * FROM \"${table}\"`\n const values: unknown[] = []\n\n if (options?.where) {\n const { whereSql, whereValues } = this.buildWhere(options.where)\n sql += whereSql\n values.push(...whereValues)\n }\n\n if (options?.orderBy) {\n sql += ` ORDER BY \"${options.orderBy.field}\" ${options.orderBy.direction === 'desc' ? 'DESC' : 'ASC'}`\n }\n\n if (options?.limit !== undefined) {\n sql += ` LIMIT ?`\n values.push(options.limit)\n }\n\n if (options?.offset !== undefined) {\n sql += ` OFFSET ?`\n values.push(options.offset)\n }\n\n const rows = this.getDb().prepare(sql).all(...values)\n return rows.flatMap((r) => asJsonObject(r) ? [asJsonObject(r)!] : [])\n }\n\n async tableGet(table: string, filter: Record<string, unknown>): Promise<Record<string, unknown> | null> {\n const { whereSql, whereValues } = this.buildWhere(filter)\n const row = this.getDb().prepare(`SELECT * FROM \"${table}\"${whereSql} LIMIT 1`).get(...whereValues)\n return asJsonObject(row)\n }\n\n async tableCount(table: string, filter?: Record<string, unknown>): Promise<number> {\n let sql = `SELECT COUNT(*) as count FROM \"${table}\"`\n const values: unknown[] = []\n\n if (filter) {\n const { whereSql, whereValues } = this.buildWhere(filter)\n sql += whereSql\n values.push(...whereValues)\n }\n\n const row = asJsonObject(this.getDb().prepare(sql).get(...values))\n return typeof row?.count === 'number' ? row.count : 0\n }\n\n private buildWhere(filter: Record<string, unknown>): { whereSql: string; whereValues: unknown[] } {\n const conditions: string[] = []\n const values: unknown[] = []\n for (const [k, v] of Object.entries(filter)) {\n conditions.push(`\"${k}\" = ?`)\n values.push(typeof v === 'object' && v !== null ? JSON.stringify(v) : v)\n }\n const whereSql = conditions.length > 0 ? ` WHERE ${conditions.join(' AND ')}` : ''\n return { whereSql, whereValues: values }\n }\n}\n\nexport default SqliteSettingsBackend\n","import type { ProviderRegistration } from '@camstack/types'\nimport { BaseAddon, RUNTIME_DEFAULTS, errMsg, settingsStoreCapability } from '@camstack/types'\nimport { SqliteSettingsBackend } from './sqlite-settings-backend.js'\n\n/**\n * SQLite Settings addon — provides persistent settings storage.\n * Capability: 'settings-store' (singleton)\n */\nexport class SqliteSettingsAddon extends BaseAddon {\n private backend: SqliteSettingsBackend | null = null\n\n constructor() { super({}) }\n\n protected async onInitialize(): Promise<ProviderRegistration[]> {\n const addonId = this.ctx.id.replace('addon:', '')\n let pathSource = 'fallback (hardcoded)'\n const dbPath = await this.ctx.api.storage.resolve.query({ location: 'addons-data', relativePath: `${addonId}/camstack.db` })\n .then((p: string) => { pathSource = 'storage capability (addons-data)'; return p })\n .catch(() => {\n if (this.ctx.dataDir) { pathSource = 'dataDir'; return `${this.ctx.dataDir}/camstack.db` }\n return 'camstack-data/addons-data/sqlite-settings/camstack.db'\n })\n\n let dbExists = false\n try {\n const fs = await import('node:fs')\n dbExists = fs.existsSync(dbPath)\n } catch (err) { this.ctx.logger.warn('Failed to check DB file existence', { meta: { error: errMsg(err) } }) }\n\n this.ctx.logger.info('DB path resolved', { meta: { pathSource, dbPath } })\n this.ctx.logger.info('DB file status', { meta: { dbExists } })\n\n this.backend = new SqliteSettingsBackend(dbPath, { ...RUNTIME_DEFAULTS })\n await this.backend.initialize()\n this.ctx.logger.info('Initialized successfully')\n return [{ capability: settingsStoreCapability, provider: this.backend }]\n }\n\n protected async onShutdown(): Promise<void> {\n await this.backend?.shutdown()\n }\n\n getBackend(): SqliteSettingsBackend | null {\n return this.backend\n }\n}\n\nexport default SqliteSettingsAddon\n","import Database from 'better-sqlite3'\nimport { CORE_TABLE_DDL, CORE_TABLE_MIGRATIONS } from './sql-schema.js'\nimport { RUNTIME_DEFAULTS } from '@camstack/types'\n\n/**\n * Thin wrapper over better-sqlite3 that manages the four settings tables:\n * system_settings, addon_settings, provider_settings, device_settings.\n *\n * All values are stored as JSON text and deserialized on read.\n */\nexport class SettingsStore {\n private readonly db: Database.Database\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n this.initTables()\n }\n\n // ---------------------------------------------------------------------------\n // System settings\n // ---------------------------------------------------------------------------\n\n getSystem(key: string): unknown {\n const row = this.db\n .prepare<[string], { value: string }>('SELECT value FROM system_settings WHERE key = ?')\n .get(key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setSystem(key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(key, JSON.stringify(value))\n }\n\n getAllSystem(): Record<string, unknown> {\n const rows = this.db\n .prepare<[], { key: string; value: string }>('SELECT key, value FROM system_settings')\n .all()\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Addon settings\n // ---------------------------------------------------------------------------\n\n getAddon(addonId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM addon_settings WHERE addon_id = ? AND key = ?',\n )\n .get(addonId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setAddon(addonId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(addon_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(addonId, key, JSON.stringify(value))\n }\n\n getAllAddon(addonId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM addon_settings WHERE addon_id = ?',\n )\n .all(addonId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n /** Bulk-replace all keys for an addon (within a transaction). */\n setAllAddon(addonId: string, config: Record<string, unknown>): void {\n const deleteStmt = this.db.prepare('DELETE FROM addon_settings WHERE addon_id = ?')\n const insertStmt = this.db.prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n deleteStmt.run(addonId)\n for (const [key, value] of Object.entries(config)) {\n insertStmt.run(addonId, key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Provider settings\n // ---------------------------------------------------------------------------\n\n getProvider(providerId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM provider_settings WHERE provider_id = ? AND key = ?',\n )\n .get(providerId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setProvider(providerId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO provider_settings (provider_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(provider_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(providerId, key, JSON.stringify(value))\n }\n\n getAllProvider(providerId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM provider_settings WHERE provider_id = ?',\n )\n .all(providerId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Device settings\n // ---------------------------------------------------------------------------\n\n getDevice(deviceId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM device_settings WHERE device_id = ? AND key = ?',\n )\n .get(deviceId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setDevice(deviceId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO device_settings (device_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(device_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(deviceId, key, JSON.stringify(value))\n }\n\n getAllDevice(deviceId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM device_settings WHERE device_id = ?',\n )\n .all(deviceId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Addon-device settings (per-device overrides of an addon's config)\n //\n // Implements the third level of the resolution chain used by the\n // multi-level settings system:\n // schema default -> addon_settings (global) -> addon_device_settings\n // ---------------------------------------------------------------------------\n\n getAddonDevice(addonId: string, deviceId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string, string], { key: string; value: string }>(\n 'SELECT key, value FROM addon_device_settings WHERE addon_id = ? AND device_id = ?',\n )\n .all(addonId, deviceId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n /**\n * Bulk-replace all per-device overrides for (addonId, deviceId) in a\n * single transaction. Empty input clears the override set — caller\n * should use `clearAddonDevice` for explicit resets.\n */\n setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void {\n const deleteStmt = this.db.prepare(\n 'DELETE FROM addon_device_settings WHERE addon_id = ? AND device_id = ?',\n )\n const insertStmt = this.db.prepare(\n `INSERT INTO addon_device_settings (addon_id, device_id, key, value, updated_at)\n VALUES (?, ?, ?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n deleteStmt.run(addonId, deviceId)\n for (const [key, value] of Object.entries(values)) {\n insertStmt.run(addonId, deviceId, key, JSON.stringify(value))\n }\n })()\n }\n\n clearAddonDevice(addonId: string, deviceId: string): void {\n this.db\n .prepare('DELETE FROM addon_device_settings WHERE addon_id = ? AND device_id = ?')\n .run(addonId, deviceId)\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /** Close the SQLite connection (call on shutdown). */\n close(): void {\n this.db.close()\n }\n\n /** Check if system_settings is empty (used for first-boot seeding). */\n isSystemSettingsEmpty(): boolean {\n const row = this.db\n .prepare<[], { cnt: number }>('SELECT COUNT(*) AS cnt FROM system_settings')\n .get()\n return (row?.cnt ?? 0) === 0\n }\n\n /** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */\n seedDefaults(): void {\n const insert = this.db.prepare(\n `INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) {\n insert.run(key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private initTables(): void {\n this.db.transaction(() => {\n for (const stmt of CORE_TABLE_DDL) {\n this.db.prepare(stmt).run()\n }\n // Idempotent additive column migrations. Each ALTER statement may\n // fail on installs where the column already exists (fresh\n // installs, or a previous boot ran the same migration). We\n // swallow those specific errors and re-throw anything else —\n // a genuine schema failure should still crash startup.\n for (const stmt of CORE_TABLE_MIGRATIONS) {\n try {\n this.db.prepare(stmt).run()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes('duplicate column name') || msg.includes('no such table')) continue\n throw err\n }\n }\n })()\n }\n}\n","/** Core table DDL statements -- executed on first boot */\nexport const CORE_TABLE_DDL: readonly string[] = [\n // Settings tables\n `CREATE TABLE IF NOT EXISTS system_settings (\n key TEXT PRIMARY KEY,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n )`,\n `CREATE TABLE IF NOT EXISTS addon_settings (\n addon_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS provider_settings (\n provider_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (provider_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS device_settings (\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (device_id, key)\n )`,\n /**\n * Per-device overrides of an addon's settings (scope: 'device' fields).\n * Resolution chain: schema default -> addon_settings (global) -> this.\n */\n `CREATE TABLE IF NOT EXISTS addon_device_settings (\n addon_id TEXT NOT NULL,\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, device_id, key)\n )`,\n\n // P12b sweep: removed static DDL for `detection_events`, `audio_levels`,\n // `track_trails` — these were consumed exclusively by the old\n // analytics sub-addon (deleted) and its analysis-data-persistence cap.\n // addon-pipeline-analytics now owns all detection/audio/track tables\n // via `declareCollection` (pipeline-analytics:motion-events,\n // :object-events, :audio-events, :tracks, :media).\n\n // Device registry\n `CREATE TABLE IF NOT EXISTS devices (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n addon_id TEXT NOT NULL,\n stable_id TEXT NOT NULL,\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n parent_stable_id TEXT,\n role TEXT,\n enabled INTEGER DEFAULT 1,\n created_at TEXT DEFAULT (datetime('now')),\n UNIQUE(addon_id, stable_id)\n)`,\n `CREATE INDEX IF NOT EXISTS idx_devices_addon ON devices(addon_id)`,\n `CREATE INDEX IF NOT EXISTS idx_devices_parent ON devices(addon_id, parent_stable_id)`,\n\n // Addon / device config store\n // stable_id IS NULL -> addon-global row (one per addon_id)\n // stable_id IS NOT NULL -> per-device row (one per addon_id + stable_id pair)\n // Two partial unique indexes enforce the constraint because SQLite does not\n // support expressions inside UNIQUE(...) declarations.\n `CREATE TABLE IF NOT EXISTS addon_config (\n addon_id TEXT NOT NULL,\n stable_id TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n updated_at TEXT DEFAULT (datetime('now'))\n)`,\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_addon_config_global ON addon_config(addon_id) WHERE stable_id IS NULL`,\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_addon_config_device ON addon_config(addon_id, stable_id) WHERE stable_id IS NOT NULL`,\n `CREATE INDEX IF NOT EXISTS idx_addon_config_addon ON addon_config(addon_id)`,\n]\n\n/**\n * Idempotent ALTER statements run after `CORE_TABLE_DDL`. Each entry\n * may fail on installs where the column already exists — the runner\n * catches \"duplicate column name\" / \"no such table\" errors and moves\n * on. Order matters: migrations stack in chronological order.\n *\n * Pattern when adding a new column:\n * 1. Add the column to the matching `CREATE TABLE` above so fresh\n * installs get it by default.\n * 2. Append an `ALTER TABLE ... ADD COLUMN ...` here so existing\n * installs get it on next boot.\n */\nexport const CORE_TABLE_MIGRATIONS: readonly string[] = [\n // 2026-04 — DeviceRole support: optional role hint per device, used by\n // admin UI to pick icons/widgets for accessory children (siren,\n // floodlight, PIR, chime, doorbell, …). Nullable; existing rows stay.\n `ALTER TABLE devices ADD COLUMN role TEXT`,\n]\n\n/** Addon table schema declaration */\nexport interface AddonTableSchema {\n readonly name: string\n readonly columns: ReadonlyArray<{\n readonly name: string\n readonly type: 'TEXT' | 'INTEGER' | 'REAL' | 'JSON'\n readonly primaryKey?: boolean\n readonly notNull?: boolean\n }>\n readonly indexes?: ReadonlyArray<{\n readonly name: string\n readonly columns: readonly string[]\n readonly unique?: boolean\n }>\n}\n\n/** Generate CREATE TABLE DDL from addon schema */\nexport function addonTableToDdl(schema: AddonTableSchema): string[] {\n const pks = schema.columns.filter(c => c.primaryKey).map(c => c.name)\n const colDefs = schema.columns.map(c => {\n const parts = [c.name, c.type]\n if (c.notNull) parts.push('NOT NULL')\n return parts.join(' ')\n })\n\n let ddl = `CREATE TABLE IF NOT EXISTS ${schema.name} (\\n ${colDefs.join(',\\n ')}`\n if (pks.length > 0) {\n ddl += `,\\n PRIMARY KEY (${pks.join(', ')})`\n }\n ddl += '\\n)'\n\n const stmts = [ddl]\n for (const idx of schema.indexes ?? []) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n stmts.push(`CREATE ${unique}INDEX IF NOT EXISTS ${idx.name} ON ${schema.name}(${idx.columns.join(', ')})`)\n }\n return stmts\n}\n","import type Database from 'better-sqlite3'\n\nexport interface DeviceRow {\n readonly stableId: string\n readonly type: string\n readonly name: string\n readonly parentStableId: string | null\n readonly enabled: boolean\n}\n\ninterface InsertInput {\n readonly stableId: string\n readonly type: string\n readonly name: string\n readonly parentStableId: string | null\n}\n\nexport class DeviceStore {\n constructor(private readonly db: Database.Database) {}\n\n insert(addonId: string, device: InsertInput): void {\n this.db\n .prepare(\n `INSERT INTO devices (addon_id, stable_id, type, name, parent_stable_id) VALUES (?, ?, ?, ?, ?)`,\n )\n .run(addonId, device.stableId, device.type, device.name, device.parentStableId)\n }\n\n listByAddon(addonId: string): readonly DeviceRow[] {\n return this.db\n .prepare(\n `SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ?`,\n )\n .all(addonId) as DeviceRow[]\n }\n\n listChildren(addonId: string, parentStableId: string): readonly DeviceRow[] {\n return this.db\n .prepare(\n `SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ? AND parent_stable_id = ?`,\n )\n .all(addonId, parentStableId) as DeviceRow[]\n }\n\n remove(addonId: string, stableId: string): void {\n this.db.prepare(`DELETE FROM devices WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId)\n }\n}\n","import type Database from 'better-sqlite3'\n\nexport class ConfigStore {\n constructor(private readonly db: Database.Database) {}\n\n save(addonId: string, stableId: string | null, data: Record<string, unknown>): void {\n // Two partial unique indexes enforce uniqueness (one for null, one for non-null stable_id).\n // SQLite resolves ON CONFLICT to the matching partial index automatically.\n if (stableId === null) {\n this.db\n .prepare(\n `INSERT INTO addon_config (addon_id, stable_id, data, updated_at)\n VALUES (?, NULL, ?, datetime('now'))\n ON CONFLICT(addon_id) WHERE stable_id IS NULL\n DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,\n )\n .run(addonId, JSON.stringify(data))\n } else {\n this.db\n .prepare(\n `INSERT INTO addon_config (addon_id, stable_id, data, updated_at)\n VALUES (?, ?, ?, datetime('now'))\n ON CONFLICT(addon_id, stable_id) WHERE stable_id IS NOT NULL\n DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,\n )\n .run(addonId, stableId, JSON.stringify(data))\n }\n }\n\n load(addonId: string, stableId: string | null): Record<string, unknown> {\n const row = stableId === null\n ? (this.db\n .prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`)\n .get(addonId) as { data: string } | undefined)\n : (this.db\n .prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id = ?`)\n .get(addonId, stableId) as { data: string } | undefined)\n\n return row ? (JSON.parse(row.data) as Record<string, unknown>) : {}\n }\n\n remove(addonId: string, stableId: string | null): void {\n if (stableId === null) {\n this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`).run(addonId)\n } else {\n this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId)\n }\n }\n}\n"],"mappings":"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","names":["import_node","import_types","Database","import_types","import_better_sqlite3","import_types","Database"]}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/builtins/sqlite-storage/sql-schema.ts","../../../src/builtins/sqlite-storage/settings-store.ts","../../../src/builtins/sqlite-storage/device-store.ts","../../../src/builtins/sqlite-storage/config-store.ts"],"sourcesContent":["/** Core table DDL statements -- executed on first boot */\nexport const CORE_TABLE_DDL: readonly string[] = [\n // Settings tables\n `CREATE TABLE IF NOT EXISTS system_settings (\n key TEXT PRIMARY KEY,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n )`,\n `CREATE TABLE IF NOT EXISTS addon_settings (\n addon_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS provider_settings (\n provider_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (provider_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS device_settings (\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (device_id, key)\n )`,\n /**\n * Per-device overrides of an addon's settings (scope: 'device' fields).\n * Resolution chain: schema default -> addon_settings (global) -> this.\n */\n `CREATE TABLE IF NOT EXISTS addon_device_settings (\n addon_id TEXT NOT NULL,\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, device_id, key)\n )`,\n\n // P12b sweep: removed static DDL for `detection_events`, `audio_levels`,\n // `track_trails` — these were consumed exclusively by the old\n // analytics sub-addon (deleted) and its analysis-data-persistence cap.\n // addon-pipeline-analytics now owns all detection/audio/track tables\n // via `declareCollection` (pipeline-analytics:motion-events,\n // :object-events, :audio-events, :tracks, :media).\n\n // Device registry\n `CREATE TABLE IF NOT EXISTS devices (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n addon_id TEXT NOT NULL,\n stable_id TEXT NOT NULL,\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n parent_stable_id TEXT,\n role TEXT,\n enabled INTEGER DEFAULT 1,\n created_at TEXT DEFAULT (datetime('now')),\n UNIQUE(addon_id, stable_id)\n)`,\n `CREATE INDEX IF NOT EXISTS idx_devices_addon ON devices(addon_id)`,\n `CREATE INDEX IF NOT EXISTS idx_devices_parent ON devices(addon_id, parent_stable_id)`,\n\n // Addon / device config store\n // stable_id IS NULL -> addon-global row (one per addon_id)\n // stable_id IS NOT NULL -> per-device row (one per addon_id + stable_id pair)\n // Two partial unique indexes enforce the constraint because SQLite does not\n // support expressions inside UNIQUE(...) declarations.\n `CREATE TABLE IF NOT EXISTS addon_config (\n addon_id TEXT NOT NULL,\n stable_id TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n updated_at TEXT DEFAULT (datetime('now'))\n)`,\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_addon_config_global ON addon_config(addon_id) WHERE stable_id IS NULL`,\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_addon_config_device ON addon_config(addon_id, stable_id) WHERE stable_id IS NOT NULL`,\n `CREATE INDEX IF NOT EXISTS idx_addon_config_addon ON addon_config(addon_id)`,\n]\n\n/**\n * Idempotent ALTER statements run after `CORE_TABLE_DDL`. Each entry\n * may fail on installs where the column already exists — the runner\n * catches \"duplicate column name\" / \"no such table\" errors and moves\n * on. Order matters: migrations stack in chronological order.\n *\n * Pattern when adding a new column:\n * 1. Add the column to the matching `CREATE TABLE` above so fresh\n * installs get it by default.\n * 2. Append an `ALTER TABLE ... ADD COLUMN ...` here so existing\n * installs get it on next boot.\n */\nexport const CORE_TABLE_MIGRATIONS: readonly string[] = [\n // 2026-04 — DeviceRole support: optional role hint per device, used by\n // admin UI to pick icons/widgets for accessory children (siren,\n // floodlight, PIR, chime, doorbell, …). Nullable; existing rows stay.\n `ALTER TABLE devices ADD COLUMN role TEXT`,\n]\n\n/** Addon table schema declaration */\nexport interface AddonTableSchema {\n readonly name: string\n readonly columns: ReadonlyArray<{\n readonly name: string\n readonly type: 'TEXT' | 'INTEGER' | 'REAL' | 'JSON'\n readonly primaryKey?: boolean\n readonly notNull?: boolean\n }>\n readonly indexes?: ReadonlyArray<{\n readonly name: string\n readonly columns: readonly string[]\n readonly unique?: boolean\n }>\n}\n\n/** Generate CREATE TABLE DDL from addon schema */\nexport function addonTableToDdl(schema: AddonTableSchema): string[] {\n const pks = schema.columns.filter(c => c.primaryKey).map(c => c.name)\n const colDefs = schema.columns.map(c => {\n const parts = [c.name, c.type]\n if (c.notNull) parts.push('NOT NULL')\n return parts.join(' ')\n })\n\n let ddl = `CREATE TABLE IF NOT EXISTS ${schema.name} (\\n ${colDefs.join(',\\n ')}`\n if (pks.length > 0) {\n ddl += `,\\n PRIMARY KEY (${pks.join(', ')})`\n }\n ddl += '\\n)'\n\n const stmts = [ddl]\n for (const idx of schema.indexes ?? []) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n stmts.push(`CREATE ${unique}INDEX IF NOT EXISTS ${idx.name} ON ${schema.name}(${idx.columns.join(', ')})`)\n }\n return stmts\n}\n","import Database from 'better-sqlite3'\nimport { CORE_TABLE_DDL, CORE_TABLE_MIGRATIONS } from './sql-schema.js'\nimport { RUNTIME_DEFAULTS } from '@camstack/types'\n\n/**\n * Thin wrapper over better-sqlite3 that manages the four settings tables:\n * system_settings, addon_settings, provider_settings, device_settings.\n *\n * All values are stored as JSON text and deserialized on read.\n */\nexport class SettingsStore {\n private readonly db: Database.Database\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n this.initTables()\n }\n\n // ---------------------------------------------------------------------------\n // System settings\n // ---------------------------------------------------------------------------\n\n getSystem(key: string): unknown {\n const row = this.db\n .prepare<[string], { value: string }>('SELECT value FROM system_settings WHERE key = ?')\n .get(key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setSystem(key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(key, JSON.stringify(value))\n }\n\n getAllSystem(): Record<string, unknown> {\n const rows = this.db\n .prepare<[], { key: string; value: string }>('SELECT key, value FROM system_settings')\n .all()\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Addon settings\n // ---------------------------------------------------------------------------\n\n getAddon(addonId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM addon_settings WHERE addon_id = ? AND key = ?',\n )\n .get(addonId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setAddon(addonId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(addon_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(addonId, key, JSON.stringify(value))\n }\n\n getAllAddon(addonId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM addon_settings WHERE addon_id = ?',\n )\n .all(addonId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n /** Bulk-replace all keys for an addon (within a transaction). */\n setAllAddon(addonId: string, config: Record<string, unknown>): void {\n const deleteStmt = this.db.prepare('DELETE FROM addon_settings WHERE addon_id = ?')\n const insertStmt = this.db.prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n deleteStmt.run(addonId)\n for (const [key, value] of Object.entries(config)) {\n insertStmt.run(addonId, key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Provider settings\n // ---------------------------------------------------------------------------\n\n getProvider(providerId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM provider_settings WHERE provider_id = ? AND key = ?',\n )\n .get(providerId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setProvider(providerId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO provider_settings (provider_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(provider_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(providerId, key, JSON.stringify(value))\n }\n\n getAllProvider(providerId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM provider_settings WHERE provider_id = ?',\n )\n .all(providerId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Device settings\n // ---------------------------------------------------------------------------\n\n getDevice(deviceId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM device_settings WHERE device_id = ? AND key = ?',\n )\n .get(deviceId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setDevice(deviceId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO device_settings (device_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(device_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(deviceId, key, JSON.stringify(value))\n }\n\n getAllDevice(deviceId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM device_settings WHERE device_id = ?',\n )\n .all(deviceId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Addon-device settings (per-device overrides of an addon's config)\n //\n // Implements the third level of the resolution chain used by the\n // multi-level settings system:\n // schema default -> addon_settings (global) -> addon_device_settings\n // ---------------------------------------------------------------------------\n\n getAddonDevice(addonId: string, deviceId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string, string], { key: string; value: string }>(\n 'SELECT key, value FROM addon_device_settings WHERE addon_id = ? AND device_id = ?',\n )\n .all(addonId, deviceId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n /**\n * Bulk-replace all per-device overrides for (addonId, deviceId) in a\n * single transaction. Empty input clears the override set — caller\n * should use `clearAddonDevice` for explicit resets.\n */\n setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void {\n const deleteStmt = this.db.prepare(\n 'DELETE FROM addon_device_settings WHERE addon_id = ? AND device_id = ?',\n )\n const insertStmt = this.db.prepare(\n `INSERT INTO addon_device_settings (addon_id, device_id, key, value, updated_at)\n VALUES (?, ?, ?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n deleteStmt.run(addonId, deviceId)\n for (const [key, value] of Object.entries(values)) {\n insertStmt.run(addonId, deviceId, key, JSON.stringify(value))\n }\n })()\n }\n\n clearAddonDevice(addonId: string, deviceId: string): void {\n this.db\n .prepare('DELETE FROM addon_device_settings WHERE addon_id = ? AND device_id = ?')\n .run(addonId, deviceId)\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /** Close the SQLite connection (call on shutdown). */\n close(): void {\n this.db.close()\n }\n\n /** Check if system_settings is empty (used for first-boot seeding). */\n isSystemSettingsEmpty(): boolean {\n const row = this.db\n .prepare<[], { cnt: number }>('SELECT COUNT(*) AS cnt FROM system_settings')\n .get()\n return (row?.cnt ?? 0) === 0\n }\n\n /** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */\n seedDefaults(): void {\n const insert = this.db.prepare(\n `INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) {\n insert.run(key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private initTables(): void {\n this.db.transaction(() => {\n for (const stmt of CORE_TABLE_DDL) {\n this.db.prepare(stmt).run()\n }\n // Idempotent additive column migrations. Each ALTER statement may\n // fail on installs where the column already exists (fresh\n // installs, or a previous boot ran the same migration). We\n // swallow those specific errors and re-throw anything else —\n // a genuine schema failure should still crash startup.\n for (const stmt of CORE_TABLE_MIGRATIONS) {\n try {\n this.db.prepare(stmt).run()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes('duplicate column name') || msg.includes('no such table')) continue\n throw err\n }\n }\n })()\n }\n}\n","import type Database from 'better-sqlite3'\n\nexport interface DeviceRow {\n readonly stableId: string\n readonly type: string\n readonly name: string\n readonly parentStableId: string | null\n readonly enabled: boolean\n}\n\ninterface InsertInput {\n readonly stableId: string\n readonly type: string\n readonly name: string\n readonly parentStableId: string | null\n}\n\nexport class DeviceStore {\n constructor(private readonly db: Database.Database) {}\n\n insert(addonId: string, device: InsertInput): void {\n this.db\n .prepare(\n `INSERT INTO devices (addon_id, stable_id, type, name, parent_stable_id) VALUES (?, ?, ?, ?, ?)`,\n )\n .run(addonId, device.stableId, device.type, device.name, device.parentStableId)\n }\n\n listByAddon(addonId: string): readonly DeviceRow[] {\n return this.db\n .prepare(\n `SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ?`,\n )\n .all(addonId) as DeviceRow[]\n }\n\n listChildren(addonId: string, parentStableId: string): readonly DeviceRow[] {\n return this.db\n .prepare(\n `SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ? AND parent_stable_id = ?`,\n )\n .all(addonId, parentStableId) as DeviceRow[]\n }\n\n remove(addonId: string, stableId: string): void {\n this.db.prepare(`DELETE FROM devices WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId)\n }\n}\n","import type Database from 'better-sqlite3'\n\nexport class ConfigStore {\n constructor(private readonly db: Database.Database) {}\n\n save(addonId: string, stableId: string | null, data: Record<string, unknown>): void {\n // Two partial unique indexes enforce uniqueness (one for null, one for non-null stable_id).\n // SQLite resolves ON CONFLICT to the matching partial index automatically.\n if (stableId === null) {\n this.db\n .prepare(\n `INSERT INTO addon_config (addon_id, stable_id, data, updated_at)\n VALUES (?, NULL, ?, datetime('now'))\n ON CONFLICT(addon_id) WHERE stable_id IS NULL\n DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,\n )\n .run(addonId, JSON.stringify(data))\n } else {\n this.db\n .prepare(\n `INSERT INTO addon_config (addon_id, stable_id, data, updated_at)\n VALUES (?, ?, ?, datetime('now'))\n ON CONFLICT(addon_id, stable_id) WHERE stable_id IS NOT NULL\n DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,\n )\n .run(addonId, stableId, JSON.stringify(data))\n }\n }\n\n load(addonId: string, stableId: string | null): Record<string, unknown> {\n const row = stableId === null\n ? (this.db\n .prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`)\n .get(addonId) as { data: string } | undefined)\n : (this.db\n .prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id = ?`)\n .get(addonId, stableId) as { data: string } | undefined)\n\n return row ? (JSON.parse(row.data) as Record<string, unknown>) : {}\n }\n\n remove(addonId: string, stableId: string | null): void {\n if (stableId === null) {\n this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`).run(addonId)\n } else {\n this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId)\n }\n }\n}\n"],"mappings":"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"}
@@ -1,28 +1,270 @@
1
- import {
2
- CORE_TABLE_DDL,
3
- ConfigStore,
4
- DeviceStore,
5
- FilesystemStorageProvider,
6
- SettingsStore,
7
- addonTableToDdl
8
- } from "../../chunk-ZELBCPDC.mjs";
9
- import {
10
- FilesystemStorageAddon
11
- } from "../../chunk-UJI4LN5P.mjs";
12
- import {
13
- SqliteSettingsAddon,
14
- SqliteSettingsBackend
15
- } from "../../chunk-FZN56HGQ.mjs";
16
- export {
17
- CORE_TABLE_DDL,
18
- ConfigStore,
19
- DeviceStore,
20
- FilesystemStorageAddon,
21
- FilesystemStorageProvider,
22
- SettingsStore,
23
- SqliteSettingsAddon,
24
- SqliteSettingsBackend,
25
- addonTableToDdl,
26
- FilesystemStorageAddon as default
1
+ import { FilesystemStorageAddon, t as FilesystemStorageProvider } from "./filesystem-storage.addon.mjs";
2
+ import { SqliteSettingsAddon, t as SqliteSettingsBackend } from "./sqlite-settings.addon.mjs";
3
+ import { RUNTIME_DEFAULTS } from "@camstack/types";
4
+ import Database from "better-sqlite3";
5
+ //#region src/builtins/sqlite-storage/sql-schema.ts
6
+ /** Core table DDL statements -- executed on first boot */
7
+ var CORE_TABLE_DDL = [
8
+ `CREATE TABLE IF NOT EXISTS system_settings (
9
+ key TEXT PRIMARY KEY,
10
+ value JSON NOT NULL,
11
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch())
12
+ )`,
13
+ `CREATE TABLE IF NOT EXISTS addon_settings (
14
+ addon_id TEXT NOT NULL,
15
+ key TEXT NOT NULL,
16
+ value JSON NOT NULL,
17
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
18
+ PRIMARY KEY (addon_id, key)
19
+ )`,
20
+ `CREATE TABLE IF NOT EXISTS provider_settings (
21
+ provider_id TEXT NOT NULL,
22
+ key TEXT NOT NULL,
23
+ value JSON NOT NULL,
24
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
25
+ PRIMARY KEY (provider_id, key)
26
+ )`,
27
+ `CREATE TABLE IF NOT EXISTS device_settings (
28
+ device_id TEXT NOT NULL,
29
+ key TEXT NOT NULL,
30
+ value JSON NOT NULL,
31
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
32
+ PRIMARY KEY (device_id, key)
33
+ )`,
34
+ `CREATE TABLE IF NOT EXISTS addon_device_settings (
35
+ addon_id TEXT NOT NULL,
36
+ device_id TEXT NOT NULL,
37
+ key TEXT NOT NULL,
38
+ value JSON NOT NULL,
39
+ updated_at INTEGER NOT NULL DEFAULT (unixepoch()),
40
+ PRIMARY KEY (addon_id, device_id, key)
41
+ )`,
42
+ `CREATE TABLE IF NOT EXISTS devices (
43
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
44
+ addon_id TEXT NOT NULL,
45
+ stable_id TEXT NOT NULL,
46
+ type TEXT NOT NULL,
47
+ name TEXT NOT NULL,
48
+ parent_stable_id TEXT,
49
+ role TEXT,
50
+ enabled INTEGER DEFAULT 1,
51
+ created_at TEXT DEFAULT (datetime('now')),
52
+ UNIQUE(addon_id, stable_id)
53
+ )`,
54
+ `CREATE INDEX IF NOT EXISTS idx_devices_addon ON devices(addon_id)`,
55
+ `CREATE INDEX IF NOT EXISTS idx_devices_parent ON devices(addon_id, parent_stable_id)`,
56
+ `CREATE TABLE IF NOT EXISTS addon_config (
57
+ addon_id TEXT NOT NULL,
58
+ stable_id TEXT,
59
+ data TEXT NOT NULL DEFAULT '{}',
60
+ updated_at TEXT DEFAULT (datetime('now'))
61
+ )`,
62
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_addon_config_global ON addon_config(addon_id) WHERE stable_id IS NULL`,
63
+ `CREATE UNIQUE INDEX IF NOT EXISTS idx_addon_config_device ON addon_config(addon_id, stable_id) WHERE stable_id IS NOT NULL`,
64
+ `CREATE INDEX IF NOT EXISTS idx_addon_config_addon ON addon_config(addon_id)`
65
+ ];
66
+ /**
67
+ * Idempotent ALTER statements run after `CORE_TABLE_DDL`. Each entry
68
+ * may fail on installs where the column already exists — the runner
69
+ * catches "duplicate column name" / "no such table" errors and moves
70
+ * on. Order matters: migrations stack in chronological order.
71
+ *
72
+ * Pattern when adding a new column:
73
+ * 1. Add the column to the matching `CREATE TABLE` above so fresh
74
+ * installs get it by default.
75
+ * 2. Append an `ALTER TABLE ... ADD COLUMN ...` here so existing
76
+ * installs get it on next boot.
77
+ */
78
+ var CORE_TABLE_MIGRATIONS = [`ALTER TABLE devices ADD COLUMN role TEXT`];
79
+ /** Generate CREATE TABLE DDL from addon schema */
80
+ function addonTableToDdl(schema) {
81
+ const pks = schema.columns.filter((c) => c.primaryKey).map((c) => c.name);
82
+ const colDefs = schema.columns.map((c) => {
83
+ const parts = [c.name, c.type];
84
+ if (c.notNull) parts.push("NOT NULL");
85
+ return parts.join(" ");
86
+ });
87
+ let ddl = `CREATE TABLE IF NOT EXISTS ${schema.name} (\n ${colDefs.join(",\n ")}`;
88
+ if (pks.length > 0) ddl += `,\n PRIMARY KEY (${pks.join(", ")})`;
89
+ ddl += "\n)";
90
+ const stmts = [ddl];
91
+ for (const idx of schema.indexes ?? []) {
92
+ const unique = idx.unique ? "UNIQUE " : "";
93
+ stmts.push(`CREATE ${unique}INDEX IF NOT EXISTS ${idx.name} ON ${schema.name}(${idx.columns.join(", ")})`);
94
+ }
95
+ return stmts;
96
+ }
97
+ //#endregion
98
+ //#region src/builtins/sqlite-storage/settings-store.ts
99
+ /**
100
+ * Thin wrapper over better-sqlite3 that manages the four settings tables:
101
+ * system_settings, addon_settings, provider_settings, device_settings.
102
+ *
103
+ * All values are stored as JSON text and deserialized on read.
104
+ */
105
+ var SettingsStore = class {
106
+ db;
107
+ constructor(dbPath) {
108
+ this.db = new Database(dbPath);
109
+ this.db.pragma("journal_mode = WAL");
110
+ this.db.pragma("foreign_keys = ON");
111
+ this.initTables();
112
+ }
113
+ getSystem(key) {
114
+ const row = this.db.prepare("SELECT value FROM system_settings WHERE key = ?").get(key);
115
+ if (row === void 0) return void 0;
116
+ return JSON.parse(row.value);
117
+ }
118
+ setSystem(key, value) {
119
+ this.db.prepare(`INSERT INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())
120
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`).run(key, JSON.stringify(value));
121
+ }
122
+ getAllSystem() {
123
+ const rows = this.db.prepare("SELECT key, value FROM system_settings").all();
124
+ return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
125
+ }
126
+ getAddon(addonId, key) {
127
+ const row = this.db.prepare("SELECT value FROM addon_settings WHERE addon_id = ? AND key = ?").get(addonId, key);
128
+ if (row === void 0) return void 0;
129
+ return JSON.parse(row.value);
130
+ }
131
+ setAddon(addonId, key, value) {
132
+ this.db.prepare(`INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())
133
+ ON CONFLICT(addon_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`).run(addonId, key, JSON.stringify(value));
134
+ }
135
+ getAllAddon(addonId) {
136
+ const rows = this.db.prepare("SELECT key, value FROM addon_settings WHERE addon_id = ?").all(addonId);
137
+ return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
138
+ }
139
+ /** Bulk-replace all keys for an addon (within a transaction). */
140
+ setAllAddon(addonId, config) {
141
+ const deleteStmt = this.db.prepare("DELETE FROM addon_settings WHERE addon_id = ?");
142
+ const insertStmt = this.db.prepare(`INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())`);
143
+ this.db.transaction(() => {
144
+ deleteStmt.run(addonId);
145
+ for (const [key, value] of Object.entries(config)) insertStmt.run(addonId, key, JSON.stringify(value));
146
+ })();
147
+ }
148
+ getProvider(providerId, key) {
149
+ const row = this.db.prepare("SELECT value FROM provider_settings WHERE provider_id = ? AND key = ?").get(providerId, key);
150
+ if (row === void 0) return void 0;
151
+ return JSON.parse(row.value);
152
+ }
153
+ setProvider(providerId, key, value) {
154
+ this.db.prepare(`INSERT INTO provider_settings (provider_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())
155
+ ON CONFLICT(provider_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`).run(providerId, key, JSON.stringify(value));
156
+ }
157
+ getAllProvider(providerId) {
158
+ const rows = this.db.prepare("SELECT key, value FROM provider_settings WHERE provider_id = ?").all(providerId);
159
+ return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
160
+ }
161
+ getDevice(deviceId, key) {
162
+ const row = this.db.prepare("SELECT value FROM device_settings WHERE device_id = ? AND key = ?").get(deviceId, key);
163
+ if (row === void 0) return void 0;
164
+ return JSON.parse(row.value);
165
+ }
166
+ setDevice(deviceId, key, value) {
167
+ this.db.prepare(`INSERT INTO device_settings (device_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())
168
+ ON CONFLICT(device_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`).run(deviceId, key, JSON.stringify(value));
169
+ }
170
+ getAllDevice(deviceId) {
171
+ const rows = this.db.prepare("SELECT key, value FROM device_settings WHERE device_id = ?").all(deviceId);
172
+ return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
173
+ }
174
+ getAddonDevice(addonId, deviceId) {
175
+ const rows = this.db.prepare("SELECT key, value FROM addon_device_settings WHERE addon_id = ? AND device_id = ?").all(addonId, deviceId);
176
+ return Object.fromEntries(rows.map((r) => [r.key, JSON.parse(r.value)]));
177
+ }
178
+ /**
179
+ * Bulk-replace all per-device overrides for (addonId, deviceId) in a
180
+ * single transaction. Empty input clears the override set — caller
181
+ * should use `clearAddonDevice` for explicit resets.
182
+ */
183
+ setAddonDevice(addonId, deviceId, values) {
184
+ const deleteStmt = this.db.prepare("DELETE FROM addon_device_settings WHERE addon_id = ? AND device_id = ?");
185
+ const insertStmt = this.db.prepare(`INSERT INTO addon_device_settings (addon_id, device_id, key, value, updated_at)
186
+ VALUES (?, ?, ?, json(?), unixepoch())`);
187
+ this.db.transaction(() => {
188
+ deleteStmt.run(addonId, deviceId);
189
+ for (const [key, value] of Object.entries(values)) insertStmt.run(addonId, deviceId, key, JSON.stringify(value));
190
+ })();
191
+ }
192
+ clearAddonDevice(addonId, deviceId) {
193
+ this.db.prepare("DELETE FROM addon_device_settings WHERE addon_id = ? AND device_id = ?").run(addonId, deviceId);
194
+ }
195
+ /** Close the SQLite connection (call on shutdown). */
196
+ close() {
197
+ this.db.close();
198
+ }
199
+ /** Check if system_settings is empty (used for first-boot seeding). */
200
+ isSystemSettingsEmpty() {
201
+ return (this.db.prepare("SELECT COUNT(*) AS cnt FROM system_settings").get()?.cnt ?? 0) === 0;
202
+ }
203
+ /** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */
204
+ seedDefaults() {
205
+ const insert = this.db.prepare(`INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())`);
206
+ this.db.transaction(() => {
207
+ for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) insert.run(key, JSON.stringify(value));
208
+ })();
209
+ }
210
+ initTables() {
211
+ this.db.transaction(() => {
212
+ for (const stmt of CORE_TABLE_DDL) this.db.prepare(stmt).run();
213
+ for (const stmt of CORE_TABLE_MIGRATIONS) try {
214
+ this.db.prepare(stmt).run();
215
+ } catch (err) {
216
+ const msg = err instanceof Error ? err.message : String(err);
217
+ if (msg.includes("duplicate column name") || msg.includes("no such table")) continue;
218
+ throw err;
219
+ }
220
+ })();
221
+ }
27
222
  };
223
+ //#endregion
224
+ //#region src/builtins/sqlite-storage/device-store.ts
225
+ var DeviceStore = class {
226
+ constructor(db) {
227
+ this.db = db;
228
+ }
229
+ insert(addonId, device) {
230
+ 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);
231
+ }
232
+ listByAddon(addonId) {
233
+ return this.db.prepare(`SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ?`).all(addonId);
234
+ }
235
+ listChildren(addonId, parentStableId) {
236
+ 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);
237
+ }
238
+ remove(addonId, stableId) {
239
+ this.db.prepare(`DELETE FROM devices WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId);
240
+ }
241
+ };
242
+ //#endregion
243
+ //#region src/builtins/sqlite-storage/config-store.ts
244
+ var ConfigStore = class {
245
+ constructor(db) {
246
+ this.db = db;
247
+ }
248
+ save(addonId, stableId, data) {
249
+ if (stableId === null) this.db.prepare(`INSERT INTO addon_config (addon_id, stable_id, data, updated_at)
250
+ VALUES (?, NULL, ?, datetime('now'))
251
+ ON CONFLICT(addon_id) WHERE stable_id IS NULL
252
+ DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`).run(addonId, JSON.stringify(data));
253
+ else this.db.prepare(`INSERT INTO addon_config (addon_id, stable_id, data, updated_at)
254
+ VALUES (?, ?, ?, datetime('now'))
255
+ ON CONFLICT(addon_id, stable_id) WHERE stable_id IS NOT NULL
256
+ DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`).run(addonId, stableId, JSON.stringify(data));
257
+ }
258
+ load(addonId, stableId) {
259
+ 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);
260
+ return row ? JSON.parse(row.data) : {};
261
+ }
262
+ remove(addonId, stableId) {
263
+ if (stableId === null) this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`).run(addonId);
264
+ else this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId);
265
+ }
266
+ };
267
+ //#endregion
268
+ export { CORE_TABLE_DDL, ConfigStore, DeviceStore, FilesystemStorageAddon, FilesystemStorageAddon as default, FilesystemStorageProvider, SettingsStore, SqliteSettingsAddon, SqliteSettingsBackend, addonTableToDdl };
269
+
28
270
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../src/builtins/sqlite-storage/sql-schema.ts","../../../src/builtins/sqlite-storage/settings-store.ts","../../../src/builtins/sqlite-storage/device-store.ts","../../../src/builtins/sqlite-storage/config-store.ts"],"sourcesContent":["/** Core table DDL statements -- executed on first boot */\nexport const CORE_TABLE_DDL: readonly string[] = [\n // Settings tables\n `CREATE TABLE IF NOT EXISTS system_settings (\n key TEXT PRIMARY KEY,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch())\n )`,\n `CREATE TABLE IF NOT EXISTS addon_settings (\n addon_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS provider_settings (\n provider_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (provider_id, key)\n )`,\n `CREATE TABLE IF NOT EXISTS device_settings (\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (device_id, key)\n )`,\n /**\n * Per-device overrides of an addon's settings (scope: 'device' fields).\n * Resolution chain: schema default -> addon_settings (global) -> this.\n */\n `CREATE TABLE IF NOT EXISTS addon_device_settings (\n addon_id TEXT NOT NULL,\n device_id TEXT NOT NULL,\n key TEXT NOT NULL,\n value JSON NOT NULL,\n updated_at INTEGER NOT NULL DEFAULT (unixepoch()),\n PRIMARY KEY (addon_id, device_id, key)\n )`,\n\n // P12b sweep: removed static DDL for `detection_events`, `audio_levels`,\n // `track_trails` — these were consumed exclusively by the old\n // analytics sub-addon (deleted) and its analysis-data-persistence cap.\n // addon-pipeline-analytics now owns all detection/audio/track tables\n // via `declareCollection` (pipeline-analytics:motion-events,\n // :object-events, :audio-events, :tracks, :media).\n\n // Device registry\n `CREATE TABLE IF NOT EXISTS devices (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n addon_id TEXT NOT NULL,\n stable_id TEXT NOT NULL,\n type TEXT NOT NULL,\n name TEXT NOT NULL,\n parent_stable_id TEXT,\n role TEXT,\n enabled INTEGER DEFAULT 1,\n created_at TEXT DEFAULT (datetime('now')),\n UNIQUE(addon_id, stable_id)\n)`,\n `CREATE INDEX IF NOT EXISTS idx_devices_addon ON devices(addon_id)`,\n `CREATE INDEX IF NOT EXISTS idx_devices_parent ON devices(addon_id, parent_stable_id)`,\n\n // Addon / device config store\n // stable_id IS NULL -> addon-global row (one per addon_id)\n // stable_id IS NOT NULL -> per-device row (one per addon_id + stable_id pair)\n // Two partial unique indexes enforce the constraint because SQLite does not\n // support expressions inside UNIQUE(...) declarations.\n `CREATE TABLE IF NOT EXISTS addon_config (\n addon_id TEXT NOT NULL,\n stable_id TEXT,\n data TEXT NOT NULL DEFAULT '{}',\n updated_at TEXT DEFAULT (datetime('now'))\n)`,\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_addon_config_global ON addon_config(addon_id) WHERE stable_id IS NULL`,\n `CREATE UNIQUE INDEX IF NOT EXISTS idx_addon_config_device ON addon_config(addon_id, stable_id) WHERE stable_id IS NOT NULL`,\n `CREATE INDEX IF NOT EXISTS idx_addon_config_addon ON addon_config(addon_id)`,\n]\n\n/**\n * Idempotent ALTER statements run after `CORE_TABLE_DDL`. Each entry\n * may fail on installs where the column already exists — the runner\n * catches \"duplicate column name\" / \"no such table\" errors and moves\n * on. Order matters: migrations stack in chronological order.\n *\n * Pattern when adding a new column:\n * 1. Add the column to the matching `CREATE TABLE` above so fresh\n * installs get it by default.\n * 2. Append an `ALTER TABLE ... ADD COLUMN ...` here so existing\n * installs get it on next boot.\n */\nexport const CORE_TABLE_MIGRATIONS: readonly string[] = [\n // 2026-04 — DeviceRole support: optional role hint per device, used by\n // admin UI to pick icons/widgets for accessory children (siren,\n // floodlight, PIR, chime, doorbell, …). Nullable; existing rows stay.\n `ALTER TABLE devices ADD COLUMN role TEXT`,\n]\n\n/** Addon table schema declaration */\nexport interface AddonTableSchema {\n readonly name: string\n readonly columns: ReadonlyArray<{\n readonly name: string\n readonly type: 'TEXT' | 'INTEGER' | 'REAL' | 'JSON'\n readonly primaryKey?: boolean\n readonly notNull?: boolean\n }>\n readonly indexes?: ReadonlyArray<{\n readonly name: string\n readonly columns: readonly string[]\n readonly unique?: boolean\n }>\n}\n\n/** Generate CREATE TABLE DDL from addon schema */\nexport function addonTableToDdl(schema: AddonTableSchema): string[] {\n const pks = schema.columns.filter(c => c.primaryKey).map(c => c.name)\n const colDefs = schema.columns.map(c => {\n const parts = [c.name, c.type]\n if (c.notNull) parts.push('NOT NULL')\n return parts.join(' ')\n })\n\n let ddl = `CREATE TABLE IF NOT EXISTS ${schema.name} (\\n ${colDefs.join(',\\n ')}`\n if (pks.length > 0) {\n ddl += `,\\n PRIMARY KEY (${pks.join(', ')})`\n }\n ddl += '\\n)'\n\n const stmts = [ddl]\n for (const idx of schema.indexes ?? []) {\n const unique = idx.unique ? 'UNIQUE ' : ''\n stmts.push(`CREATE ${unique}INDEX IF NOT EXISTS ${idx.name} ON ${schema.name}(${idx.columns.join(', ')})`)\n }\n return stmts\n}\n","import Database from 'better-sqlite3'\nimport { CORE_TABLE_DDL, CORE_TABLE_MIGRATIONS } from './sql-schema.js'\nimport { RUNTIME_DEFAULTS } from '@camstack/types'\n\n/**\n * Thin wrapper over better-sqlite3 that manages the four settings tables:\n * system_settings, addon_settings, provider_settings, device_settings.\n *\n * All values are stored as JSON text and deserialized on read.\n */\nexport class SettingsStore {\n private readonly db: Database.Database\n\n constructor(dbPath: string) {\n this.db = new Database(dbPath)\n this.db.pragma('journal_mode = WAL')\n this.db.pragma('foreign_keys = ON')\n this.initTables()\n }\n\n // ---------------------------------------------------------------------------\n // System settings\n // ---------------------------------------------------------------------------\n\n getSystem(key: string): unknown {\n const row = this.db\n .prepare<[string], { value: string }>('SELECT value FROM system_settings WHERE key = ?')\n .get(key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setSystem(key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())\n ON CONFLICT(key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(key, JSON.stringify(value))\n }\n\n getAllSystem(): Record<string, unknown> {\n const rows = this.db\n .prepare<[], { key: string; value: string }>('SELECT key, value FROM system_settings')\n .all()\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Addon settings\n // ---------------------------------------------------------------------------\n\n getAddon(addonId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM addon_settings WHERE addon_id = ? AND key = ?',\n )\n .get(addonId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setAddon(addonId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(addon_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(addonId, key, JSON.stringify(value))\n }\n\n getAllAddon(addonId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM addon_settings WHERE addon_id = ?',\n )\n .all(addonId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n /** Bulk-replace all keys for an addon (within a transaction). */\n setAllAddon(addonId: string, config: Record<string, unknown>): void {\n const deleteStmt = this.db.prepare('DELETE FROM addon_settings WHERE addon_id = ?')\n const insertStmt = this.db.prepare(\n `INSERT INTO addon_settings (addon_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n deleteStmt.run(addonId)\n for (const [key, value] of Object.entries(config)) {\n insertStmt.run(addonId, key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Provider settings\n // ---------------------------------------------------------------------------\n\n getProvider(providerId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM provider_settings WHERE provider_id = ? AND key = ?',\n )\n .get(providerId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setProvider(providerId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO provider_settings (provider_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(provider_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(providerId, key, JSON.stringify(value))\n }\n\n getAllProvider(providerId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM provider_settings WHERE provider_id = ?',\n )\n .all(providerId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Device settings\n // ---------------------------------------------------------------------------\n\n getDevice(deviceId: string, key: string): unknown {\n const row = this.db\n .prepare<[string, string], { value: string }>(\n 'SELECT value FROM device_settings WHERE device_id = ? AND key = ?',\n )\n .get(deviceId, key)\n if (row === undefined) return undefined\n return JSON.parse(row.value)\n }\n\n setDevice(deviceId: string, key: string, value: unknown): void {\n this.db\n .prepare(\n `INSERT INTO device_settings (device_id, key, value, updated_at) VALUES (?, ?, json(?), unixepoch())\n ON CONFLICT(device_id, key) DO UPDATE SET value = excluded.value, updated_at = excluded.updated_at`,\n )\n .run(deviceId, key, JSON.stringify(value))\n }\n\n getAllDevice(deviceId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string], { key: string; value: string }>(\n 'SELECT key, value FROM device_settings WHERE device_id = ?',\n )\n .all(deviceId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n // ---------------------------------------------------------------------------\n // Addon-device settings (per-device overrides of an addon's config)\n //\n // Implements the third level of the resolution chain used by the\n // multi-level settings system:\n // schema default -> addon_settings (global) -> addon_device_settings\n // ---------------------------------------------------------------------------\n\n getAddonDevice(addonId: string, deviceId: string): Record<string, unknown> {\n const rows = this.db\n .prepare<[string, string], { key: string; value: string }>(\n 'SELECT key, value FROM addon_device_settings WHERE addon_id = ? AND device_id = ?',\n )\n .all(addonId, deviceId)\n return Object.fromEntries(rows.map(r => [r.key, JSON.parse(r.value)]))\n }\n\n /**\n * Bulk-replace all per-device overrides for (addonId, deviceId) in a\n * single transaction. Empty input clears the override set — caller\n * should use `clearAddonDevice` for explicit resets.\n */\n setAddonDevice(addonId: string, deviceId: string, values: Record<string, unknown>): void {\n const deleteStmt = this.db.prepare(\n 'DELETE FROM addon_device_settings WHERE addon_id = ? AND device_id = ?',\n )\n const insertStmt = this.db.prepare(\n `INSERT INTO addon_device_settings (addon_id, device_id, key, value, updated_at)\n VALUES (?, ?, ?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n deleteStmt.run(addonId, deviceId)\n for (const [key, value] of Object.entries(values)) {\n insertStmt.run(addonId, deviceId, key, JSON.stringify(value))\n }\n })()\n }\n\n clearAddonDevice(addonId: string, deviceId: string): void {\n this.db\n .prepare('DELETE FROM addon_device_settings WHERE addon_id = ? AND device_id = ?')\n .run(addonId, deviceId)\n }\n\n // ---------------------------------------------------------------------------\n // Lifecycle\n // ---------------------------------------------------------------------------\n\n /** Close the SQLite connection (call on shutdown). */\n close(): void {\n this.db.close()\n }\n\n /** Check if system_settings is empty (used for first-boot seeding). */\n isSystemSettingsEmpty(): boolean {\n const row = this.db\n .prepare<[], { cnt: number }>('SELECT COUNT(*) AS cnt FROM system_settings')\n .get()\n return (row?.cnt ?? 0) === 0\n }\n\n /** Seed system_settings with RUNTIME_DEFAULTS (only on first boot). */\n seedDefaults(): void {\n const insert = this.db.prepare(\n `INSERT OR IGNORE INTO system_settings (key, value, updated_at) VALUES (?, json(?), unixepoch())`,\n )\n this.db.transaction(() => {\n for (const [key, value] of Object.entries(RUNTIME_DEFAULTS)) {\n insert.run(key, JSON.stringify(value))\n }\n })()\n }\n\n // ---------------------------------------------------------------------------\n // Private helpers\n // ---------------------------------------------------------------------------\n\n private initTables(): void {\n this.db.transaction(() => {\n for (const stmt of CORE_TABLE_DDL) {\n this.db.prepare(stmt).run()\n }\n // Idempotent additive column migrations. Each ALTER statement may\n // fail on installs where the column already exists (fresh\n // installs, or a previous boot ran the same migration). We\n // swallow those specific errors and re-throw anything else —\n // a genuine schema failure should still crash startup.\n for (const stmt of CORE_TABLE_MIGRATIONS) {\n try {\n this.db.prepare(stmt).run()\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err)\n if (msg.includes('duplicate column name') || msg.includes('no such table')) continue\n throw err\n }\n }\n })()\n }\n}\n","import type Database from 'better-sqlite3'\n\nexport interface DeviceRow {\n readonly stableId: string\n readonly type: string\n readonly name: string\n readonly parentStableId: string | null\n readonly enabled: boolean\n}\n\ninterface InsertInput {\n readonly stableId: string\n readonly type: string\n readonly name: string\n readonly parentStableId: string | null\n}\n\nexport class DeviceStore {\n constructor(private readonly db: Database.Database) {}\n\n insert(addonId: string, device: InsertInput): void {\n this.db\n .prepare(\n `INSERT INTO devices (addon_id, stable_id, type, name, parent_stable_id) VALUES (?, ?, ?, ?, ?)`,\n )\n .run(addonId, device.stableId, device.type, device.name, device.parentStableId)\n }\n\n listByAddon(addonId: string): readonly DeviceRow[] {\n return this.db\n .prepare(\n `SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ?`,\n )\n .all(addonId) as DeviceRow[]\n }\n\n listChildren(addonId: string, parentStableId: string): readonly DeviceRow[] {\n return this.db\n .prepare(\n `SELECT stable_id as stableId, type, name, parent_stable_id as parentStableId, enabled FROM devices WHERE addon_id = ? AND parent_stable_id = ?`,\n )\n .all(addonId, parentStableId) as DeviceRow[]\n }\n\n remove(addonId: string, stableId: string): void {\n this.db.prepare(`DELETE FROM devices WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId)\n }\n}\n","import type Database from 'better-sqlite3'\n\nexport class ConfigStore {\n constructor(private readonly db: Database.Database) {}\n\n save(addonId: string, stableId: string | null, data: Record<string, unknown>): void {\n // Two partial unique indexes enforce uniqueness (one for null, one for non-null stable_id).\n // SQLite resolves ON CONFLICT to the matching partial index automatically.\n if (stableId === null) {\n this.db\n .prepare(\n `INSERT INTO addon_config (addon_id, stable_id, data, updated_at)\n VALUES (?, NULL, ?, datetime('now'))\n ON CONFLICT(addon_id) WHERE stable_id IS NULL\n DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,\n )\n .run(addonId, JSON.stringify(data))\n } else {\n this.db\n .prepare(\n `INSERT INTO addon_config (addon_id, stable_id, data, updated_at)\n VALUES (?, ?, ?, datetime('now'))\n ON CONFLICT(addon_id, stable_id) WHERE stable_id IS NOT NULL\n DO UPDATE SET data = excluded.data, updated_at = excluded.updated_at`,\n )\n .run(addonId, stableId, JSON.stringify(data))\n }\n }\n\n load(addonId: string, stableId: string | null): Record<string, unknown> {\n const row = stableId === null\n ? (this.db\n .prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`)\n .get(addonId) as { data: string } | undefined)\n : (this.db\n .prepare(`SELECT data FROM addon_config WHERE addon_id = ? AND stable_id = ?`)\n .get(addonId, stableId) as { data: string } | undefined)\n\n return row ? (JSON.parse(row.data) as Record<string, unknown>) : {}\n }\n\n remove(addonId: string, stableId: string | null): void {\n if (stableId === null) {\n this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id IS NULL`).run(addonId)\n } else {\n this.db.prepare(`DELETE FROM addon_config WHERE addon_id = ? AND stable_id = ?`).run(addonId, stableId)\n }\n }\n}\n"],"mappings":"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"}