@camstack/system 1.2.29 → 1.2.31

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 (53) hide show
  1. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  2. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  3. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/alerts/alerts.addon.js +1 -1
  6. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  7. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +135 -92
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +135 -92
  9. package/dist/builtins/backup-orchestrator/destination-policy.d.ts +12 -14
  10. package/dist/builtins/backup-orchestrator/schedule-store.d.ts +6 -6
  11. package/dist/builtins/console-logging/index.js +1 -1
  12. package/dist/builtins/console-logging/index.mjs +1 -1
  13. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  14. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  15. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  16. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  17. package/dist/builtins/hub-forwarder/index.js +1 -1
  18. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  19. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  20. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  21. package/dist/builtins/local-network/local-network.addon.js +1 -1
  22. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  23. package/dist/builtins/loki-logging/index.js +1 -1
  24. package/dist/builtins/loki-logging/index.mjs +1 -1
  25. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  26. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  27. package/dist/builtins/platform-probe/index.js +1 -1
  28. package/dist/builtins/platform-probe/index.mjs +1 -1
  29. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  30. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  31. package/dist/builtins/snapshot/index.js +1 -1
  32. package/dist/builtins/snapshot/index.mjs +1 -1
  33. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  34. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  35. package/dist/builtins/sqlite-storage/filter-compiler.d.ts +61 -0
  36. package/dist/builtins/sqlite-storage/integration-registry.d.ts +3 -3
  37. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +49 -8
  38. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +117 -85
  39. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +117 -85
  40. package/dist/builtins/storage-orchestrator/location-store.d.ts +17 -24
  41. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.d.ts +0 -6
  42. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +132 -121
  43. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +132 -121
  44. package/dist/builtins/system-config/system-config.addon.js +1 -1
  45. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  46. package/dist/builtins/winston-logging/index.js +1 -1
  47. package/dist/builtins/winston-logging/index.mjs +1 -1
  48. package/dist/{dist-CPmFYLqq.js → dist-CDv5YTHQ.js} +93 -1
  49. package/dist/{dist-B7hOpr5i.mjs → dist-CNmXITJ-.mjs} +93 -1
  50. package/dist/index.js +406 -224
  51. package/dist/index.mjs +406 -224
  52. package/dist/kernel/addon-installer.d.ts +38 -0
  53. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("./chunk-Cek0wNdY.js");
3
- const require_dist = require("./dist-CPmFYLqq.js");
3
+ const require_dist = require("./dist-CDv5YTHQ.js");
4
4
  const require_model_download_service = require("./model-download-service-hf0ookyy.js");
5
5
  const require_manifest_python_deps = require("./manifest-python-deps-BqE5j0-O.js");
6
6
  const require_resource_monitor = require("./resource-monitor-DNNomR-i.js");
@@ -2077,18 +2077,47 @@ function serializeSetting(value) {
2077
2077
  valueType: "json"
2078
2078
  };
2079
2079
  }
2080
- function deserializeSetting(value, valueType) {
2080
+ /**
2081
+ * Decode a settings row back to its value.
2082
+ *
2083
+ * `raw` is `unknown`, not `string`, on purpose: the collection surface parses
2084
+ * any TEXT column whose value starts with `{` or `[`, so a `json` setting
2085
+ * arrives here ALREADY an object. Coercing it with `String(...)` first — which
2086
+ * is what a straight port of the `table*` version did — produced the literal
2087
+ * `"[object Object]"`.
2088
+ */
2089
+ function deserializeSetting(raw, valueType) {
2090
+ if (valueType === "json") {
2091
+ if (typeof raw !== "string") return raw;
2092
+ try {
2093
+ return JSON.parse(raw);
2094
+ } catch {
2095
+ return raw;
2096
+ }
2097
+ }
2098
+ const value = String(raw);
2081
2099
  switch (valueType) {
2082
2100
  case "number": return Number(value);
2083
2101
  case "boolean": return value === "true";
2084
- case "json": try {
2085
- return JSON.parse(value);
2086
- } catch {
2087
- return value;
2088
- }
2089
2102
  default: return value;
2090
2103
  }
2091
2104
  }
2105
+ /**
2106
+ * Read an `info` blob off a row.
2107
+ *
2108
+ * The column is declared `JSON`, so the surface hands back a parsed object —
2109
+ * but rows written before B2 by an older build are still raw strings on disk
2110
+ * for as long as they go un-rewritten, and a string that fails to parse must
2111
+ * not take the whole record down.
2112
+ */
2113
+ function isPlainObject(value) {
2114
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2115
+ }
2116
+ function readInfo(raw) {
2117
+ if (typeof raw === "string") return require_dist.parseJsonObject(raw) ?? {};
2118
+ if (isPlainObject(raw)) return { ...raw };
2119
+ return {};
2120
+ }
2092
2121
  var integrationCounter = 0;
2093
2122
  var deviceCounter = 0;
2094
2123
  function nextIntegrationId() {
@@ -2097,52 +2126,50 @@ function nextIntegrationId() {
2097
2126
  function nextDeviceId() {
2098
2127
  return `dev_${String(++deviceCounter).padStart(4, "0")}`;
2099
2128
  }
2100
- var INTEGRATIONS_SCHEMA = {
2101
- columns: [
2102
- {
2103
- name: "id",
2104
- type: "TEXT",
2105
- primaryKey: true
2106
- },
2107
- {
2108
- name: "addon_id",
2109
- type: "TEXT",
2110
- notNull: true
2111
- },
2112
- {
2113
- name: "name",
2114
- type: "TEXT",
2115
- notNull: true
2116
- },
2117
- {
2118
- name: "enabled",
2119
- type: "INTEGER",
2120
- notNull: true,
2121
- defaultValue: 1
2122
- },
2123
- {
2124
- name: "info",
2125
- type: "TEXT",
2126
- notNull: true,
2127
- defaultValue: "{}"
2128
- },
2129
- {
2130
- name: "created_at",
2131
- type: "INTEGER",
2132
- notNull: true
2133
- },
2134
- {
2135
- name: "updated_at",
2136
- type: "INTEGER",
2137
- notNull: true
2138
- }
2139
- ],
2140
- indexes: [{
2141
- name: "idx_integrations_addon",
2142
- columns: ["addon_id"]
2143
- }]
2144
- };
2145
- var INTEGRATION_SETTINGS_SCHEMA = { columns: [
2129
+ var INTEGRATIONS_COLUMNS = [
2130
+ {
2131
+ name: "id",
2132
+ type: "TEXT",
2133
+ primaryKey: true
2134
+ },
2135
+ {
2136
+ name: "addon_id",
2137
+ type: "TEXT",
2138
+ notNull: true
2139
+ },
2140
+ {
2141
+ name: "name",
2142
+ type: "TEXT",
2143
+ notNull: true
2144
+ },
2145
+ {
2146
+ name: "enabled",
2147
+ type: "INTEGER",
2148
+ notNull: true,
2149
+ defaultValue: 1
2150
+ },
2151
+ {
2152
+ name: "info",
2153
+ type: "JSON",
2154
+ notNull: true,
2155
+ defaultValue: "{}"
2156
+ },
2157
+ {
2158
+ name: "created_at",
2159
+ type: "INTEGER",
2160
+ notNull: true
2161
+ },
2162
+ {
2163
+ name: "updated_at",
2164
+ type: "INTEGER",
2165
+ notNull: true
2166
+ }
2167
+ ];
2168
+ var INTEGRATIONS_INDEXES = [{
2169
+ name: "idx_integrations_addon",
2170
+ columns: ["addon_id"]
2171
+ }];
2172
+ var INTEGRATION_SETTINGS_COLUMNS = [
2146
2173
  {
2147
2174
  name: "integration_id",
2148
2175
  type: "TEXT",
@@ -2164,76 +2191,74 @@ var INTEGRATION_SETTINGS_SCHEMA = { columns: [
2164
2191
  notNull: true,
2165
2192
  defaultValue: "string"
2166
2193
  }
2167
- ] };
2168
- var DEVICES_SCHEMA = {
2169
- columns: [
2170
- {
2171
- name: "id",
2172
- type: "TEXT",
2173
- primaryKey: true
2174
- },
2175
- {
2176
- name: "integration_id",
2177
- type: "TEXT",
2178
- notNull: true
2179
- },
2180
- {
2181
- name: "stable_id",
2182
- type: "TEXT",
2183
- notNull: true,
2184
- unique: true
2185
- },
2186
- {
2187
- name: "type",
2188
- type: "TEXT",
2189
- notNull: true,
2190
- defaultValue: "camera"
2191
- },
2192
- {
2193
- name: "name",
2194
- type: "TEXT",
2195
- notNull: true
2196
- },
2197
- {
2198
- name: "enabled",
2199
- type: "INTEGER",
2200
- notNull: true,
2201
- defaultValue: 1
2202
- },
2203
- {
2204
- name: "info",
2205
- type: "TEXT",
2206
- notNull: true,
2207
- defaultValue: "{}"
2208
- },
2209
- {
2210
- name: "created_at",
2211
- type: "INTEGER",
2212
- notNull: true
2213
- },
2214
- {
2215
- name: "updated_at",
2216
- type: "INTEGER",
2217
- notNull: true
2218
- }
2219
- ],
2220
- indexes: [
2221
- {
2222
- name: "idx_devices_integration",
2223
- columns: ["integration_id"]
2224
- },
2225
- {
2226
- name: "idx_devices_stable",
2227
- columns: ["stable_id"],
2228
- unique: true
2229
- },
2230
- {
2231
- name: "idx_devices_type",
2232
- columns: ["type"]
2233
- }
2234
- ]
2235
- };
2236
- var DEVICE_SETTINGS_SCHEMA = { columns: [
2194
+ ];
2195
+ var DEVICES_COLUMNS = [
2196
+ {
2197
+ name: "id",
2198
+ type: "TEXT",
2199
+ primaryKey: true
2200
+ },
2201
+ {
2202
+ name: "integration_id",
2203
+ type: "TEXT",
2204
+ notNull: true
2205
+ },
2206
+ {
2207
+ name: "stable_id",
2208
+ type: "TEXT",
2209
+ notNull: true,
2210
+ unique: true
2211
+ },
2212
+ {
2213
+ name: "type",
2214
+ type: "TEXT",
2215
+ notNull: true,
2216
+ defaultValue: "camera"
2217
+ },
2218
+ {
2219
+ name: "name",
2220
+ type: "TEXT",
2221
+ notNull: true
2222
+ },
2223
+ {
2224
+ name: "enabled",
2225
+ type: "INTEGER",
2226
+ notNull: true,
2227
+ defaultValue: 1
2228
+ },
2229
+ {
2230
+ name: "info",
2231
+ type: "JSON",
2232
+ notNull: true,
2233
+ defaultValue: "{}"
2234
+ },
2235
+ {
2236
+ name: "created_at",
2237
+ type: "INTEGER",
2238
+ notNull: true
2239
+ },
2240
+ {
2241
+ name: "updated_at",
2242
+ type: "INTEGER",
2243
+ notNull: true
2244
+ }
2245
+ ];
2246
+ var DEVICES_INDEXES = [
2247
+ {
2248
+ name: "idx_devices_integration",
2249
+ columns: ["integration_id"]
2250
+ },
2251
+ {
2252
+ name: "idx_devices_stable",
2253
+ columns: ["stable_id"],
2254
+ unique: true
2255
+ },
2256
+ {
2257
+ name: "idx_devices_type",
2258
+ columns: ["type"]
2259
+ }
2260
+ ];
2261
+ var DEVICE_SETTINGS_COLUMNS = [
2237
2262
  {
2238
2263
  name: "device_id",
2239
2264
  type: "TEXT",
@@ -2255,51 +2280,80 @@ var DEVICE_SETTINGS_SCHEMA = { columns: [
2255
2280
  notNull: true,
2256
2281
  defaultValue: "string"
2257
2282
  }
2258
- ] };
2283
+ ];
2284
+ var INTEGRATIONS = "integrations";
2285
+ var INTEGRATION_SETTINGS = "integration_settings";
2286
+ var DEVICES = "devices";
2287
+ var DEVICE_SETTINGS = "device_settings_kv";
2259
2288
  var IntegrationRegistry = class {
2260
- backend;
2289
+ store;
2261
2290
  constructor(backend) {
2262
- this.backend = backend;
2291
+ this.store = backend;
2263
2292
  }
2264
2293
  async initialize() {
2265
- await this.backend.ensureTable?.("integrations", INTEGRATIONS_SCHEMA);
2266
- await this.backend.ensureTable?.("integration_settings", INTEGRATION_SETTINGS_SCHEMA);
2267
- await this.backend.ensureTable?.("devices", DEVICES_SCHEMA);
2268
- await this.backend.ensureTable?.("device_settings_kv", DEVICE_SETTINGS_SCHEMA);
2269
- const ints = await this.backend.tableQuery?.("integrations", {
2270
- orderBy: {
2271
- field: "id",
2272
- direction: "desc"
2273
- },
2274
- limit: 1
2275
- }) ?? [];
2294
+ await this.store.declareCollection({
2295
+ collection: INTEGRATIONS,
2296
+ columns: INTEGRATIONS_COLUMNS,
2297
+ indexes: INTEGRATIONS_INDEXES
2298
+ });
2299
+ await this.store.declareCollection({
2300
+ collection: INTEGRATION_SETTINGS,
2301
+ columns: INTEGRATION_SETTINGS_COLUMNS
2302
+ });
2303
+ await this.store.declareCollection({
2304
+ collection: DEVICES,
2305
+ columns: DEVICES_COLUMNS,
2306
+ indexes: DEVICES_INDEXES
2307
+ });
2308
+ await this.store.declareCollection({
2309
+ collection: DEVICE_SETTINGS,
2310
+ columns: DEVICE_SETTINGS_COLUMNS
2311
+ });
2312
+ const ints = await this.store.query({
2313
+ collection: INTEGRATIONS,
2314
+ filter: {
2315
+ orderBy: {
2316
+ field: "id",
2317
+ direction: "desc"
2318
+ },
2319
+ limit: 1
2320
+ }
2321
+ });
2276
2322
  if (ints[0]) {
2277
- const num = parseInt(String(ints[0]["id"]).replace("int_", ""), 10);
2323
+ const num = parseInt(ints[0].id.replace("int_", ""), 10);
2278
2324
  if (!isNaN(num)) integrationCounter = num;
2279
2325
  }
2280
- const devs = await this.backend.tableQuery?.("devices", {
2281
- orderBy: {
2282
- field: "id",
2283
- direction: "desc"
2284
- },
2285
- limit: 1
2286
- }) ?? [];
2326
+ const devs = await this.store.query({
2327
+ collection: DEVICES,
2328
+ filter: {
2329
+ orderBy: {
2330
+ field: "id",
2331
+ direction: "desc"
2332
+ },
2333
+ limit: 1
2334
+ }
2335
+ });
2287
2336
  if (devs[0]) {
2288
- const num = parseInt(String(devs[0]["id"]).replace("dev_", ""), 10);
2337
+ const num = parseInt(devs[0].id.replace("dev_", ""), 10);
2289
2338
  if (!isNaN(num)) deviceCounter = num;
2290
2339
  }
2291
2340
  }
2292
2341
  async createIntegration(input) {
2293
2342
  const id = nextIntegrationId();
2294
2343
  const now = Math.floor(Date.now() / 1e3);
2295
- await this.backend.tableInsert?.("integrations", {
2296
- id,
2297
- addon_id: input.addonId,
2298
- name: input.name,
2299
- enabled: input.enabled !== false ? 1 : 0,
2300
- info: JSON.stringify(input.info ?? {}),
2301
- created_at: now,
2302
- updated_at: now
2344
+ await this.store.insert({
2345
+ collection: INTEGRATIONS,
2346
+ record: {
2347
+ id,
2348
+ data: {
2349
+ addon_id: input.addonId,
2350
+ name: input.name,
2351
+ enabled: input.enabled !== false ? 1 : 0,
2352
+ info: input.info ?? {},
2353
+ created_at: now,
2354
+ updated_at: now
2355
+ }
2356
+ }
2303
2357
  });
2304
2358
  if (input.settings) await this.setIntegrationSettings(id, input.settings);
2305
2359
  return {
@@ -2313,52 +2367,95 @@ var IntegrationRegistry = class {
2313
2367
  };
2314
2368
  }
2315
2369
  async getIntegration(id) {
2316
- const row = await this.backend.tableGet?.("integrations", { id });
2317
- return row ? this.mapIntegration(row) : null;
2370
+ const rows = await this.store.query({
2371
+ collection: INTEGRATIONS,
2372
+ filter: {
2373
+ where: { id },
2374
+ limit: 1
2375
+ }
2376
+ });
2377
+ return rows[0] ? this.mapIntegration(rows[0].data) : null;
2318
2378
  }
2319
2379
  async getIntegrationByAddonId(addonId) {
2320
- const row = await this.backend.tableGet?.("integrations", { addon_id: addonId });
2321
- return row ? this.mapIntegration(row) : null;
2380
+ const rows = await this.store.query({
2381
+ collection: INTEGRATIONS,
2382
+ filter: {
2383
+ where: { addon_id: addonId },
2384
+ limit: 1
2385
+ }
2386
+ });
2387
+ return rows[0] ? this.mapIntegration(rows[0].data) : null;
2322
2388
  }
2323
2389
  async listIntegrations() {
2324
- return (await this.backend.tableQuery?.("integrations", { orderBy: {
2325
- field: "created_at",
2326
- direction: "asc"
2327
- } }) ?? []).map((r) => this.mapIntegration(r));
2390
+ return (await this.store.query({
2391
+ collection: INTEGRATIONS,
2392
+ filter: { orderBy: {
2393
+ field: "created_at",
2394
+ direction: "asc"
2395
+ } }
2396
+ })).map((r) => this.mapIntegration(r.data));
2328
2397
  }
2329
2398
  async updateIntegration(id, updates) {
2330
2399
  const updateRow = { updated_at: Math.floor(Date.now() / 1e3) };
2331
2400
  if (updates.name !== void 0) updateRow["name"] = updates.name;
2332
2401
  if (updates.enabled !== void 0) updateRow["enabled"] = updates.enabled ? 1 : 0;
2333
- if (updates.info !== void 0) updateRow["info"] = JSON.stringify(updates.info);
2334
- await this.backend.tableUpdate?.("integrations", { id }, updateRow);
2402
+ if (updates.info !== void 0) updateRow["info"] = updates.info;
2403
+ await this.store.updateWhere({
2404
+ collection: INTEGRATIONS,
2405
+ filter: { where: { id } },
2406
+ data: updateRow
2407
+ });
2335
2408
  return this.getIntegration(id);
2336
2409
  }
2337
2410
  async deleteIntegration(id) {
2338
2411
  const devices = await this.listDevices(id);
2339
- for (const d of devices) await this.backend.tableDelete?.("device_settings_kv", { device_id: d.id });
2340
- await this.backend.tableDelete?.("devices", { integration_id: id });
2341
- await this.backend.tableDelete?.("integration_settings", { integration_id: id });
2342
- await this.backend.tableDelete?.("integrations", { id });
2412
+ for (const d of devices) await this.store.deleteWhere({
2413
+ collection: DEVICE_SETTINGS,
2414
+ filter: { where: { device_id: d.id } }
2415
+ });
2416
+ await this.store.deleteWhere({
2417
+ collection: DEVICES,
2418
+ filter: { where: { integration_id: id } }
2419
+ });
2420
+ await this.store.deleteWhere({
2421
+ collection: INTEGRATION_SETTINGS,
2422
+ filter: { where: { integration_id: id } }
2423
+ });
2424
+ await this.store.deleteWhere({
2425
+ collection: INTEGRATIONS,
2426
+ filter: { where: { id } }
2427
+ });
2343
2428
  return true;
2344
2429
  }
2345
2430
  async getIntegrationSettings(integrationId) {
2346
2431
  const result = {};
2347
- const rows = await this.backend.tableQuery?.("integration_settings", { where: { integration_id: integrationId } }) ?? [];
2348
- for (const row of rows) result[String(row["key"])] = deserializeSetting(String(row["value"]), String(row["value_type"]));
2432
+ const rows = await this.store.query({
2433
+ collection: INTEGRATION_SETTINGS,
2434
+ filter: { where: { integration_id: integrationId } }
2435
+ });
2436
+ for (const row of rows) result[String(row.data["key"])] = deserializeSetting(row.data["value"], String(row.data["value_type"]));
2349
2437
  return result;
2350
2438
  }
2351
2439
  async setIntegrationSetting(integrationId, key, value) {
2352
2440
  const s = serializeSetting(value);
2353
- await this.backend.tableDelete?.("integration_settings", {
2354
- integration_id: integrationId,
2355
- key
2441
+ await this.store.deleteWhere({
2442
+ collection: INTEGRATION_SETTINGS,
2443
+ filter: { where: {
2444
+ integration_id: integrationId,
2445
+ key
2446
+ } }
2356
2447
  });
2357
- await this.backend.tableInsert?.("integration_settings", {
2358
- integration_id: integrationId,
2359
- key,
2360
- value: s.value,
2361
- value_type: s.valueType
2448
+ await this.store.insert({
2449
+ collection: INTEGRATION_SETTINGS,
2450
+ record: {
2451
+ id: `${integrationId}:${key}`,
2452
+ data: {
2453
+ integration_id: integrationId,
2454
+ key,
2455
+ value: s.value,
2456
+ value_type: s.valueType
2457
+ }
2458
+ }
2362
2459
  });
2363
2460
  }
2364
2461
  async setIntegrationSettings(integrationId, settings) {
@@ -2367,16 +2464,21 @@ var IntegrationRegistry = class {
2367
2464
  async createDevice(input) {
2368
2465
  const id = nextDeviceId();
2369
2466
  const now = Math.floor(Date.now() / 1e3);
2370
- await this.backend.tableInsert?.("devices", {
2371
- id,
2372
- integration_id: input.integrationId,
2373
- stable_id: input.stableId,
2374
- type: input.type,
2375
- name: input.name,
2376
- enabled: input.enabled !== false ? 1 : 0,
2377
- info: JSON.stringify(input.info ?? {}),
2378
- created_at: now,
2379
- updated_at: now
2467
+ await this.store.insert({
2468
+ collection: DEVICES,
2469
+ record: {
2470
+ id,
2471
+ data: {
2472
+ integration_id: input.integrationId,
2473
+ stable_id: input.stableId,
2474
+ type: input.type,
2475
+ name: input.name,
2476
+ enabled: input.enabled !== false ? 1 : 0,
2477
+ info: input.info ?? {},
2478
+ created_at: now,
2479
+ updated_at: now
2480
+ }
2481
+ }
2380
2482
  });
2381
2483
  if (input.settings) await this.setDeviceSettings(id, input.settings);
2382
2484
  return {
@@ -2392,65 +2494,101 @@ var IntegrationRegistry = class {
2392
2494
  };
2393
2495
  }
2394
2496
  async getDevice(id) {
2395
- const row = await this.backend.tableGet?.("devices", { id });
2396
- return row ? this.mapDevice(row) : null;
2497
+ const rows = await this.store.query({
2498
+ collection: DEVICES,
2499
+ filter: {
2500
+ where: { id },
2501
+ limit: 1
2502
+ }
2503
+ });
2504
+ return rows[0] ? this.mapDevice(rows[0].data) : null;
2397
2505
  }
2398
2506
  async getDeviceByStableId(stableId) {
2399
- const row = await this.backend.tableGet?.("devices", { stable_id: stableId });
2400
- return row ? this.mapDevice(row) : null;
2507
+ const rows = await this.store.query({
2508
+ collection: DEVICES,
2509
+ filter: {
2510
+ where: { stable_id: stableId },
2511
+ limit: 1
2512
+ }
2513
+ });
2514
+ return rows[0] ? this.mapDevice(rows[0].data) : null;
2401
2515
  }
2402
2516
  async listDevices(integrationId) {
2403
- const options = integrationId ? {
2404
- where: { integration_id: integrationId },
2405
- orderBy: {
2406
- field: "created_at",
2407
- direction: "asc"
2517
+ return (await this.store.query({
2518
+ collection: DEVICES,
2519
+ filter: {
2520
+ ...integrationId ? { where: { integration_id: integrationId } } : {},
2521
+ orderBy: {
2522
+ field: "created_at",
2523
+ direction: "asc"
2524
+ }
2408
2525
  }
2409
- } : { orderBy: {
2410
- field: "created_at",
2411
- direction: "asc"
2412
- } };
2413
- return (await this.backend.tableQuery?.("devices", options) ?? []).map((r) => this.mapDevice(r));
2526
+ })).map((r) => this.mapDevice(r.data));
2414
2527
  }
2415
2528
  async listCameras() {
2416
- return (await this.backend.tableQuery?.("devices", {
2417
- where: { type: "camera" },
2418
- orderBy: {
2419
- field: "created_at",
2420
- direction: "asc"
2529
+ return (await this.store.query({
2530
+ collection: DEVICES,
2531
+ filter: {
2532
+ where: { type: "camera" },
2533
+ orderBy: {
2534
+ field: "created_at",
2535
+ direction: "asc"
2536
+ }
2421
2537
  }
2422
- }) ?? []).map((r) => this.mapDevice(r));
2538
+ })).map((r) => this.mapDevice(r.data));
2423
2539
  }
2424
2540
  async updateDevice(id, updates) {
2425
2541
  const updateRow = { updated_at: Math.floor(Date.now() / 1e3) };
2426
2542
  if (updates.name !== void 0) updateRow["name"] = updates.name;
2427
2543
  if (updates.enabled !== void 0) updateRow["enabled"] = updates.enabled ? 1 : 0;
2428
- if (updates.info !== void 0) updateRow["info"] = JSON.stringify(updates.info);
2429
- await this.backend.tableUpdate?.("devices", { id }, updateRow);
2544
+ if (updates.info !== void 0) updateRow["info"] = updates.info;
2545
+ await this.store.updateWhere({
2546
+ collection: DEVICES,
2547
+ filter: { where: { id } },
2548
+ data: updateRow
2549
+ });
2430
2550
  return this.getDevice(id);
2431
2551
  }
2432
2552
  async deleteDevice(id) {
2433
- await this.backend.tableDelete?.("device_settings_kv", { device_id: id });
2434
- await this.backend.tableDelete?.("devices", { id });
2553
+ await this.store.deleteWhere({
2554
+ collection: DEVICE_SETTINGS,
2555
+ filter: { where: { device_id: id } }
2556
+ });
2557
+ await this.store.deleteWhere({
2558
+ collection: DEVICES,
2559
+ filter: { where: { id } }
2560
+ });
2435
2561
  return true;
2436
2562
  }
2437
2563
  async getDeviceSettings(deviceId) {
2438
2564
  const result = {};
2439
- const rows = await this.backend.tableQuery?.("device_settings_kv", { where: { device_id: deviceId } }) ?? [];
2440
- for (const row of rows) result[String(row["key"])] = deserializeSetting(String(row["value"]), String(row["value_type"]));
2565
+ const rows = await this.store.query({
2566
+ collection: DEVICE_SETTINGS,
2567
+ filter: { where: { device_id: deviceId } }
2568
+ });
2569
+ for (const row of rows) result[String(row.data["key"])] = deserializeSetting(row.data["value"], String(row.data["value_type"]));
2441
2570
  return result;
2442
2571
  }
2443
2572
  async setDeviceSetting(deviceId, key, value) {
2444
2573
  const s = serializeSetting(value);
2445
- await this.backend.tableDelete?.("device_settings_kv", {
2446
- device_id: deviceId,
2447
- key
2574
+ await this.store.deleteWhere({
2575
+ collection: DEVICE_SETTINGS,
2576
+ filter: { where: {
2577
+ device_id: deviceId,
2578
+ key
2579
+ } }
2448
2580
  });
2449
- await this.backend.tableInsert?.("device_settings_kv", {
2450
- device_id: deviceId,
2451
- key,
2452
- value: s.value,
2453
- value_type: s.valueType
2581
+ await this.store.insert({
2582
+ collection: DEVICE_SETTINGS,
2583
+ record: {
2584
+ id: `${deviceId}:${key}`,
2585
+ data: {
2586
+ device_id: deviceId,
2587
+ key,
2588
+ value: s.value,
2589
+ value_type: s.valueType
2590
+ }
2591
+ }
2454
2592
  });
2455
2593
  }
2456
2594
  async setDeviceSettings(deviceId, settings) {
@@ -2462,7 +2600,7 @@ var IntegrationRegistry = class {
2462
2600
  addonId: String(row["addon_id"]),
2463
2601
  name: String(row["name"]),
2464
2602
  enabled: row["enabled"] === 1,
2465
- info: typeof row["info"] === "string" ? require_dist.parseJsonObject(row["info"]) ?? {} : {},
2603
+ info: readInfo(row["info"]),
2466
2604
  createdAt: Number(row["created_at"]),
2467
2605
  updatedAt: Number(row["updated_at"])
2468
2606
  };
@@ -2475,7 +2613,7 @@ var IntegrationRegistry = class {
2475
2613
  type: String(row["type"]),
2476
2614
  name: String(row["name"]),
2477
2615
  enabled: row["enabled"] === 1,
2478
- info: typeof row["info"] === "string" ? require_dist.parseJsonObject(row["info"]) ?? {} : {},
2616
+ info: readInfo(row["info"]),
2479
2617
  createdAt: Number(row["created_at"]),
2480
2618
  updatedAt: Number(row["updated_at"])
2481
2619
  };
@@ -3574,6 +3712,25 @@ function readPackageJson(pkgJsonPath) {
3574
3712
  camstackAddons: Array.isArray(addonsField) ? addonsField : null
3575
3713
  };
3576
3714
  }
3715
+ /**
3716
+ * Should the bootstrap SKIP seeding `packageName` into /data/addons?
3717
+ *
3718
+ * True only for the package the closure provides, and only when it actually
3719
+ * resolves. Both halves are load-bearing:
3720
+ *
3721
+ * - seeding a copy the closure already provides creates one that WINS over
3722
+ * the closure and is never refreshed (bootstrap is seed-only, and
3723
+ * `camstack deploy` of a framework package is rejected on an image hub), so
3724
+ * the node runs first-boot builtins forever while reporting the new
3725
+ * version;
3726
+ * - skipping it when it does NOT resolve is how a fresh deployment was once
3727
+ * made unbootable ("No addon provides required infrastructure capability
3728
+ * storage-provider"). That is why the guard is resolvability rather than a
3729
+ * blanket removal from the bootstrap list.
3730
+ */
3731
+ function shouldSkipClosureProvidedSeed(packageName, isResolvable) {
3732
+ return packageName === "@camstack/system" && isResolvable;
3733
+ }
3577
3734
  /** Minimal no-op logger for default parameter */
3578
3735
  var noopLogger = {
3579
3736
  debug() {},
@@ -3593,6 +3750,7 @@ var AddonInstaller = class AddonInstaller {
3593
3750
  workspaceDir;
3594
3751
  installSource;
3595
3752
  logger;
3753
+ isPackageResolvable;
3596
3754
  /**
3597
3755
  * Central manifest of installed addons. Tracks version + source +
3598
3756
  * timestamps + last-backup pointer per package, written atomically
@@ -3608,6 +3766,7 @@ var AddonInstaller = class AddonInstaller {
3608
3766
  this.workspaceDir = config.workspacePackagesDir;
3609
3767
  this.logger = logger;
3610
3768
  this.manifest = new AddonManifest(config.addonsDir);
3769
+ this.isPackageResolvable = config.isPackageResolvable;
3611
3770
  if (config.installSource) this.installSource = config.installSource;
3612
3771
  else this.installSource = "npm";
3613
3772
  }
@@ -3636,6 +3795,25 @@ var AddonInstaller = class AddonInstaller {
3636
3795
  * hardcoded lists — see design spec
3637
3796
  * docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
3638
3797
  */
3798
+ /**
3799
+ * Is `packageName` reachable through normal resolution — i.e. does the
3800
+ * running closure already provide it?
3801
+ *
3802
+ * Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
3803
+ * exercise BOTH sides. That matters more than it looks: the branch that
3804
+ * still seeds is the one protecting a fresh deployment from an unbootable
3805
+ * node, and it is unreachable in a test process where `@camstack/system`
3806
+ * always resolves.
3807
+ */
3808
+ isResolvableFromClosure(packageName) {
3809
+ if (this.isPackageResolvable) return this.isPackageResolvable(packageName);
3810
+ try {
3811
+ require.resolve(`${packageName}/package.json`);
3812
+ return true;
3813
+ } catch {
3814
+ return false;
3815
+ }
3816
+ }
3639
3817
  static deriveBootstrapList(metaPackageName) {
3640
3818
  const pkgJsonId = `${metaPackageName}/package.json`;
3641
3819
  let pkgPath;
@@ -3718,6 +3896,10 @@ var AddonInstaller = class AddonInstaller {
3718
3896
  this.logger.debug(`${packageName} — already installed, skipping`);
3719
3897
  continue;
3720
3898
  }
3899
+ if (shouldSkipClosureProvidedSeed(packageName, this.isResolvableFromClosure(packageName))) {
3900
+ this.logger.info(`${packageName} — provided by the server closure, not seeding a copy that would shadow it`);
3901
+ continue;
3902
+ }
3721
3903
  try {
3722
3904
  if (isLocal) {
3723
3905
  const pkgDir = this.findLocalPackage(packageName);