@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.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { a as __toCommonJS, i as __require, n as __esmMin, o as __toESM$1, r as __exportAll, t as __commonJSMin$1 } from "./chunk-CNf5ZN-e.mjs";
2
- import { A as extractNestedAddonId, At as scopeKey, Dt as parseJsonUnknown$1, Et as parseJsonObject, F as isObjectInput, G as objectInputDeclaresAddonId, H as looseSchema, I as isVoidInput, J as procedureAuthKey, L as kebabToCamel, Mt as EventCategory$1, N as isArrayOutputSchema, Ot as readinessKey, P as isCollectionArrayMethod, R as lifecycleJobSchema, St as expandCapMethods, V as logLevelAtMost, _t as asNumber, at as errMsg$1, bt as emitDownForOwnedCaps, c as RUNTIME_DEFAULTS, ct as DEVICE_SETTINGS_CONTRIBUTION_METHODS, gt as asJsonObject$1, kt as resolveCapMount, lt as DEVICE_STATUS_METHOD, m as addonSettingsCapability, mt as ReadinessTimeoutError, pt as ReadinessRegistry, s as METHOD_ACCESS_MAP, st as DATAPLANE_SECRET_HEADER$1, t as ALL_CAPABILITY_DEFINITIONS, vt as asString$1, yt as createEvent } from "./dist-B7hOpr5i.mjs";
2
+ import { A as extractNestedAddonId, At as scopeKey, Dt as parseJsonUnknown$1, Et as parseJsonObject, F as isObjectInput, G as objectInputDeclaresAddonId, H as looseSchema, I as isVoidInput, J as procedureAuthKey, L as kebabToCamel, Mt as EventCategory$1, N as isArrayOutputSchema, Ot as readinessKey, P as isCollectionArrayMethod, R as lifecycleJobSchema, St as expandCapMethods, V as logLevelAtMost, _t as asNumber, at as errMsg$1, bt as emitDownForOwnedCaps, c as RUNTIME_DEFAULTS, ct as DEVICE_SETTINGS_CONTRIBUTION_METHODS, gt as asJsonObject$1, kt as resolveCapMount, lt as DEVICE_STATUS_METHOD, m as addonSettingsCapability, mt as ReadinessTimeoutError, pt as ReadinessRegistry, s as METHOD_ACCESS_MAP, st as DATAPLANE_SECRET_HEADER$1, t as ALL_CAPABILITY_DEFINITIONS, vt as asString$1, yt as createEvent } from "./dist-CNmXITJ-.mjs";
3
3
  import { a as downloadModel, c as getModelFilePath, d as contentTypeFor, f as createAuthenticatedFileServer, h as resolveFilePath, i as downloadFile, l as isModelDownloaded, m as parseTokenizedUrl, n as collectModelFiles, o as ensureModel, p as parseRangeHeader, r as deleteModelFromDisk, s as fetchJson, t as ModelDownloadService, u as createFileDataPlaneHandler } from "./model-download-service-Cp9f4dk6.mjs";
4
4
  import { $ as buildNativeCapProxy, A as createHubCapForwardService, B as AGENT_CAP_FWD_ACTION, C as brokerTransportLink, Ct as resolveAddonClass, D as localProviderLink, E as ipcParentLink, F as createUdsLogger, G as callWithServiceDiscovery, H as CapRouteResolver, I as createUdsLoggerWithControl, J as UdsLocalTransportServer, K as createLocalTransport, L as LocalChildClient, M as createUdsEventBridge, N as createUdsEventBus, O as HUB_CAP_FWD_ACTION, P as udsChildLogToWorkerEntry, Q as encodeFrame, R as LocalChildRegistry, S as brokerCallForCap, St as runNpm, T as ipcChildLink, U as CapRouteError, V as AGENT_CAP_FWD_SERVICE, W as classifyCapRoute, X as localEndpointPath, Y as SocketChannel, Z as FrameDecoder, _ as createKernelHwAccel, _t as CapabilityHandle, a as getWorkerDeviceRegistry, b as __resetCapUsageRegistryForTests, bt as installManifestNativeDeps, c as setHubConnected, ct as NATIVE_PROVIDER_SERVICE_INFIX, d as getBrokerEventBus, dt as capBareAction, et as buildUdsNativeCapProxy, f as getMoleculerEventStats, ft as capServiceName, g as AddonDepsManager, gt as DeviceRegistry, h as subscribePassthrough, ht as serializeTypedArrays, i as createUdsAddonContext, it as mountNativeCapService, j as createParentUnownedCallHandler, k as HUB_CAP_FWD_SERVICE, l as EVENT_TOPIC_PREFIX, lt as capActionName, m as setNodeEventInterest, mt as deserializeTypedArrays, n as adaptBrokerToCluster, o as getOrInitReadinessRegistry, ot as createAddonService, p as registerEventBusService, pt as parseCapAction, q as UdsLocalTransportClient, r as createAddonContext, s as getOrInitReadinessRegistryForClient, st as validateProviderRegistrations, t as installManifestPythonDeps, tt as createBrokerDeviceManagerApi, u as clusterEventTopic, ut as capActionSuffix, v as resolveHwAccel, vt as CapabilityUnavailableError, w as buildLinkChain, wt as createAddonDataPlaneFacility, x as getCapUsageRegistry, xt as resolveNpmInvocation, y as CapUsageRegistry, yt as copyBundledNativeModules, z as UDS_NO_ROUTE_PREFIX } from "./manifest-python-deps-Ck4-9K9m.mjs";
5
5
  import { n as getSinglePidStats, t as getPidStats } from "./resource-monitor-BkP504Vq.mjs";
@@ -2069,18 +2069,47 @@ function serializeSetting(value) {
2069
2069
  valueType: "json"
2070
2070
  };
2071
2071
  }
2072
- function deserializeSetting(value, valueType) {
2072
+ /**
2073
+ * Decode a settings row back to its value.
2074
+ *
2075
+ * `raw` is `unknown`, not `string`, on purpose: the collection surface parses
2076
+ * any TEXT column whose value starts with `{` or `[`, so a `json` setting
2077
+ * arrives here ALREADY an object. Coercing it with `String(...)` first — which
2078
+ * is what a straight port of the `table*` version did — produced the literal
2079
+ * `"[object Object]"`.
2080
+ */
2081
+ function deserializeSetting(raw, valueType) {
2082
+ if (valueType === "json") {
2083
+ if (typeof raw !== "string") return raw;
2084
+ try {
2085
+ return JSON.parse(raw);
2086
+ } catch {
2087
+ return raw;
2088
+ }
2089
+ }
2090
+ const value = String(raw);
2073
2091
  switch (valueType) {
2074
2092
  case "number": return Number(value);
2075
2093
  case "boolean": return value === "true";
2076
- case "json": try {
2077
- return JSON.parse(value);
2078
- } catch {
2079
- return value;
2080
- }
2081
2094
  default: return value;
2082
2095
  }
2083
2096
  }
2097
+ /**
2098
+ * Read an `info` blob off a row.
2099
+ *
2100
+ * The column is declared `JSON`, so the surface hands back a parsed object —
2101
+ * but rows written before B2 by an older build are still raw strings on disk
2102
+ * for as long as they go un-rewritten, and a string that fails to parse must
2103
+ * not take the whole record down.
2104
+ */
2105
+ function isPlainObject(value) {
2106
+ return value !== null && typeof value === "object" && !Array.isArray(value);
2107
+ }
2108
+ function readInfo(raw) {
2109
+ if (typeof raw === "string") return parseJsonObject(raw) ?? {};
2110
+ if (isPlainObject(raw)) return { ...raw };
2111
+ return {};
2112
+ }
2084
2113
  var integrationCounter = 0;
2085
2114
  var deviceCounter = 0;
2086
2115
  function nextIntegrationId() {
@@ -2089,52 +2118,50 @@ function nextIntegrationId() {
2089
2118
  function nextDeviceId() {
2090
2119
  return `dev_${String(++deviceCounter).padStart(4, "0")}`;
2091
2120
  }
2092
- var INTEGRATIONS_SCHEMA = {
2093
- columns: [
2094
- {
2095
- name: "id",
2096
- type: "TEXT",
2097
- primaryKey: true
2098
- },
2099
- {
2100
- name: "addon_id",
2101
- type: "TEXT",
2102
- notNull: true
2103
- },
2104
- {
2105
- name: "name",
2106
- type: "TEXT",
2107
- notNull: true
2108
- },
2109
- {
2110
- name: "enabled",
2111
- type: "INTEGER",
2112
- notNull: true,
2113
- defaultValue: 1
2114
- },
2115
- {
2116
- name: "info",
2117
- type: "TEXT",
2118
- notNull: true,
2119
- defaultValue: "{}"
2120
- },
2121
- {
2122
- name: "created_at",
2123
- type: "INTEGER",
2124
- notNull: true
2125
- },
2126
- {
2127
- name: "updated_at",
2128
- type: "INTEGER",
2129
- notNull: true
2130
- }
2131
- ],
2132
- indexes: [{
2133
- name: "idx_integrations_addon",
2134
- columns: ["addon_id"]
2135
- }]
2136
- };
2137
- var INTEGRATION_SETTINGS_SCHEMA = { columns: [
2121
+ var INTEGRATIONS_COLUMNS = [
2122
+ {
2123
+ name: "id",
2124
+ type: "TEXT",
2125
+ primaryKey: true
2126
+ },
2127
+ {
2128
+ name: "addon_id",
2129
+ type: "TEXT",
2130
+ notNull: true
2131
+ },
2132
+ {
2133
+ name: "name",
2134
+ type: "TEXT",
2135
+ notNull: true
2136
+ },
2137
+ {
2138
+ name: "enabled",
2139
+ type: "INTEGER",
2140
+ notNull: true,
2141
+ defaultValue: 1
2142
+ },
2143
+ {
2144
+ name: "info",
2145
+ type: "JSON",
2146
+ notNull: true,
2147
+ defaultValue: "{}"
2148
+ },
2149
+ {
2150
+ name: "created_at",
2151
+ type: "INTEGER",
2152
+ notNull: true
2153
+ },
2154
+ {
2155
+ name: "updated_at",
2156
+ type: "INTEGER",
2157
+ notNull: true
2158
+ }
2159
+ ];
2160
+ var INTEGRATIONS_INDEXES = [{
2161
+ name: "idx_integrations_addon",
2162
+ columns: ["addon_id"]
2163
+ }];
2164
+ var INTEGRATION_SETTINGS_COLUMNS = [
2138
2165
  {
2139
2166
  name: "integration_id",
2140
2167
  type: "TEXT",
@@ -2156,76 +2183,74 @@ var INTEGRATION_SETTINGS_SCHEMA = { columns: [
2156
2183
  notNull: true,
2157
2184
  defaultValue: "string"
2158
2185
  }
2159
- ] };
2160
- var DEVICES_SCHEMA = {
2161
- columns: [
2162
- {
2163
- name: "id",
2164
- type: "TEXT",
2165
- primaryKey: true
2166
- },
2167
- {
2168
- name: "integration_id",
2169
- type: "TEXT",
2170
- notNull: true
2171
- },
2172
- {
2173
- name: "stable_id",
2174
- type: "TEXT",
2175
- notNull: true,
2176
- unique: true
2177
- },
2178
- {
2179
- name: "type",
2180
- type: "TEXT",
2181
- notNull: true,
2182
- defaultValue: "camera"
2183
- },
2184
- {
2185
- name: "name",
2186
- type: "TEXT",
2187
- notNull: true
2188
- },
2189
- {
2190
- name: "enabled",
2191
- type: "INTEGER",
2192
- notNull: true,
2193
- defaultValue: 1
2194
- },
2195
- {
2196
- name: "info",
2197
- type: "TEXT",
2198
- notNull: true,
2199
- defaultValue: "{}"
2200
- },
2201
- {
2202
- name: "created_at",
2203
- type: "INTEGER",
2204
- notNull: true
2205
- },
2206
- {
2207
- name: "updated_at",
2208
- type: "INTEGER",
2209
- notNull: true
2210
- }
2211
- ],
2212
- indexes: [
2213
- {
2214
- name: "idx_devices_integration",
2215
- columns: ["integration_id"]
2216
- },
2217
- {
2218
- name: "idx_devices_stable",
2219
- columns: ["stable_id"],
2220
- unique: true
2221
- },
2222
- {
2223
- name: "idx_devices_type",
2224
- columns: ["type"]
2225
- }
2226
- ]
2227
- };
2228
- var DEVICE_SETTINGS_SCHEMA = { columns: [
2186
+ ];
2187
+ var DEVICES_COLUMNS = [
2188
+ {
2189
+ name: "id",
2190
+ type: "TEXT",
2191
+ primaryKey: true
2192
+ },
2193
+ {
2194
+ name: "integration_id",
2195
+ type: "TEXT",
2196
+ notNull: true
2197
+ },
2198
+ {
2199
+ name: "stable_id",
2200
+ type: "TEXT",
2201
+ notNull: true,
2202
+ unique: true
2203
+ },
2204
+ {
2205
+ name: "type",
2206
+ type: "TEXT",
2207
+ notNull: true,
2208
+ defaultValue: "camera"
2209
+ },
2210
+ {
2211
+ name: "name",
2212
+ type: "TEXT",
2213
+ notNull: true
2214
+ },
2215
+ {
2216
+ name: "enabled",
2217
+ type: "INTEGER",
2218
+ notNull: true,
2219
+ defaultValue: 1
2220
+ },
2221
+ {
2222
+ name: "info",
2223
+ type: "JSON",
2224
+ notNull: true,
2225
+ defaultValue: "{}"
2226
+ },
2227
+ {
2228
+ name: "created_at",
2229
+ type: "INTEGER",
2230
+ notNull: true
2231
+ },
2232
+ {
2233
+ name: "updated_at",
2234
+ type: "INTEGER",
2235
+ notNull: true
2236
+ }
2237
+ ];
2238
+ var DEVICES_INDEXES = [
2239
+ {
2240
+ name: "idx_devices_integration",
2241
+ columns: ["integration_id"]
2242
+ },
2243
+ {
2244
+ name: "idx_devices_stable",
2245
+ columns: ["stable_id"],
2246
+ unique: true
2247
+ },
2248
+ {
2249
+ name: "idx_devices_type",
2250
+ columns: ["type"]
2251
+ }
2252
+ ];
2253
+ var DEVICE_SETTINGS_COLUMNS = [
2229
2254
  {
2230
2255
  name: "device_id",
2231
2256
  type: "TEXT",
@@ -2247,51 +2272,80 @@ var DEVICE_SETTINGS_SCHEMA = { columns: [
2247
2272
  notNull: true,
2248
2273
  defaultValue: "string"
2249
2274
  }
2250
- ] };
2275
+ ];
2276
+ var INTEGRATIONS = "integrations";
2277
+ var INTEGRATION_SETTINGS = "integration_settings";
2278
+ var DEVICES = "devices";
2279
+ var DEVICE_SETTINGS = "device_settings_kv";
2251
2280
  var IntegrationRegistry = class {
2252
- backend;
2281
+ store;
2253
2282
  constructor(backend) {
2254
- this.backend = backend;
2283
+ this.store = backend;
2255
2284
  }
2256
2285
  async initialize() {
2257
- await this.backend.ensureTable?.("integrations", INTEGRATIONS_SCHEMA);
2258
- await this.backend.ensureTable?.("integration_settings", INTEGRATION_SETTINGS_SCHEMA);
2259
- await this.backend.ensureTable?.("devices", DEVICES_SCHEMA);
2260
- await this.backend.ensureTable?.("device_settings_kv", DEVICE_SETTINGS_SCHEMA);
2261
- const ints = await this.backend.tableQuery?.("integrations", {
2262
- orderBy: {
2263
- field: "id",
2264
- direction: "desc"
2265
- },
2266
- limit: 1
2267
- }) ?? [];
2286
+ await this.store.declareCollection({
2287
+ collection: INTEGRATIONS,
2288
+ columns: INTEGRATIONS_COLUMNS,
2289
+ indexes: INTEGRATIONS_INDEXES
2290
+ });
2291
+ await this.store.declareCollection({
2292
+ collection: INTEGRATION_SETTINGS,
2293
+ columns: INTEGRATION_SETTINGS_COLUMNS
2294
+ });
2295
+ await this.store.declareCollection({
2296
+ collection: DEVICES,
2297
+ columns: DEVICES_COLUMNS,
2298
+ indexes: DEVICES_INDEXES
2299
+ });
2300
+ await this.store.declareCollection({
2301
+ collection: DEVICE_SETTINGS,
2302
+ columns: DEVICE_SETTINGS_COLUMNS
2303
+ });
2304
+ const ints = await this.store.query({
2305
+ collection: INTEGRATIONS,
2306
+ filter: {
2307
+ orderBy: {
2308
+ field: "id",
2309
+ direction: "desc"
2310
+ },
2311
+ limit: 1
2312
+ }
2313
+ });
2268
2314
  if (ints[0]) {
2269
- const num = parseInt(String(ints[0]["id"]).replace("int_", ""), 10);
2315
+ const num = parseInt(ints[0].id.replace("int_", ""), 10);
2270
2316
  if (!isNaN(num)) integrationCounter = num;
2271
2317
  }
2272
- const devs = await this.backend.tableQuery?.("devices", {
2273
- orderBy: {
2274
- field: "id",
2275
- direction: "desc"
2276
- },
2277
- limit: 1
2278
- }) ?? [];
2318
+ const devs = await this.store.query({
2319
+ collection: DEVICES,
2320
+ filter: {
2321
+ orderBy: {
2322
+ field: "id",
2323
+ direction: "desc"
2324
+ },
2325
+ limit: 1
2326
+ }
2327
+ });
2279
2328
  if (devs[0]) {
2280
- const num = parseInt(String(devs[0]["id"]).replace("dev_", ""), 10);
2329
+ const num = parseInt(devs[0].id.replace("dev_", ""), 10);
2281
2330
  if (!isNaN(num)) deviceCounter = num;
2282
2331
  }
2283
2332
  }
2284
2333
  async createIntegration(input) {
2285
2334
  const id = nextIntegrationId();
2286
2335
  const now = Math.floor(Date.now() / 1e3);
2287
- await this.backend.tableInsert?.("integrations", {
2288
- id,
2289
- addon_id: input.addonId,
2290
- name: input.name,
2291
- enabled: input.enabled !== false ? 1 : 0,
2292
- info: JSON.stringify(input.info ?? {}),
2293
- created_at: now,
2294
- updated_at: now
2336
+ await this.store.insert({
2337
+ collection: INTEGRATIONS,
2338
+ record: {
2339
+ id,
2340
+ data: {
2341
+ addon_id: input.addonId,
2342
+ name: input.name,
2343
+ enabled: input.enabled !== false ? 1 : 0,
2344
+ info: input.info ?? {},
2345
+ created_at: now,
2346
+ updated_at: now
2347
+ }
2348
+ }
2295
2349
  });
2296
2350
  if (input.settings) await this.setIntegrationSettings(id, input.settings);
2297
2351
  return {
@@ -2305,52 +2359,95 @@ var IntegrationRegistry = class {
2305
2359
  };
2306
2360
  }
2307
2361
  async getIntegration(id) {
2308
- const row = await this.backend.tableGet?.("integrations", { id });
2309
- return row ? this.mapIntegration(row) : null;
2362
+ const rows = await this.store.query({
2363
+ collection: INTEGRATIONS,
2364
+ filter: {
2365
+ where: { id },
2366
+ limit: 1
2367
+ }
2368
+ });
2369
+ return rows[0] ? this.mapIntegration(rows[0].data) : null;
2310
2370
  }
2311
2371
  async getIntegrationByAddonId(addonId) {
2312
- const row = await this.backend.tableGet?.("integrations", { addon_id: addonId });
2313
- return row ? this.mapIntegration(row) : null;
2372
+ const rows = await this.store.query({
2373
+ collection: INTEGRATIONS,
2374
+ filter: {
2375
+ where: { addon_id: addonId },
2376
+ limit: 1
2377
+ }
2378
+ });
2379
+ return rows[0] ? this.mapIntegration(rows[0].data) : null;
2314
2380
  }
2315
2381
  async listIntegrations() {
2316
- return (await this.backend.tableQuery?.("integrations", { orderBy: {
2317
- field: "created_at",
2318
- direction: "asc"
2319
- } }) ?? []).map((r) => this.mapIntegration(r));
2382
+ return (await this.store.query({
2383
+ collection: INTEGRATIONS,
2384
+ filter: { orderBy: {
2385
+ field: "created_at",
2386
+ direction: "asc"
2387
+ } }
2388
+ })).map((r) => this.mapIntegration(r.data));
2320
2389
  }
2321
2390
  async updateIntegration(id, updates) {
2322
2391
  const updateRow = { updated_at: Math.floor(Date.now() / 1e3) };
2323
2392
  if (updates.name !== void 0) updateRow["name"] = updates.name;
2324
2393
  if (updates.enabled !== void 0) updateRow["enabled"] = updates.enabled ? 1 : 0;
2325
- if (updates.info !== void 0) updateRow["info"] = JSON.stringify(updates.info);
2326
- await this.backend.tableUpdate?.("integrations", { id }, updateRow);
2394
+ if (updates.info !== void 0) updateRow["info"] = updates.info;
2395
+ await this.store.updateWhere({
2396
+ collection: INTEGRATIONS,
2397
+ filter: { where: { id } },
2398
+ data: updateRow
2399
+ });
2327
2400
  return this.getIntegration(id);
2328
2401
  }
2329
2402
  async deleteIntegration(id) {
2330
2403
  const devices = await this.listDevices(id);
2331
- for (const d of devices) await this.backend.tableDelete?.("device_settings_kv", { device_id: d.id });
2332
- await this.backend.tableDelete?.("devices", { integration_id: id });
2333
- await this.backend.tableDelete?.("integration_settings", { integration_id: id });
2334
- await this.backend.tableDelete?.("integrations", { id });
2404
+ for (const d of devices) await this.store.deleteWhere({
2405
+ collection: DEVICE_SETTINGS,
2406
+ filter: { where: { device_id: d.id } }
2407
+ });
2408
+ await this.store.deleteWhere({
2409
+ collection: DEVICES,
2410
+ filter: { where: { integration_id: id } }
2411
+ });
2412
+ await this.store.deleteWhere({
2413
+ collection: INTEGRATION_SETTINGS,
2414
+ filter: { where: { integration_id: id } }
2415
+ });
2416
+ await this.store.deleteWhere({
2417
+ collection: INTEGRATIONS,
2418
+ filter: { where: { id } }
2419
+ });
2335
2420
  return true;
2336
2421
  }
2337
2422
  async getIntegrationSettings(integrationId) {
2338
2423
  const result = {};
2339
- const rows = await this.backend.tableQuery?.("integration_settings", { where: { integration_id: integrationId } }) ?? [];
2340
- for (const row of rows) result[String(row["key"])] = deserializeSetting(String(row["value"]), String(row["value_type"]));
2424
+ const rows = await this.store.query({
2425
+ collection: INTEGRATION_SETTINGS,
2426
+ filter: { where: { integration_id: integrationId } }
2427
+ });
2428
+ for (const row of rows) result[String(row.data["key"])] = deserializeSetting(row.data["value"], String(row.data["value_type"]));
2341
2429
  return result;
2342
2430
  }
2343
2431
  async setIntegrationSetting(integrationId, key, value) {
2344
2432
  const s = serializeSetting(value);
2345
- await this.backend.tableDelete?.("integration_settings", {
2346
- integration_id: integrationId,
2347
- key
2433
+ await this.store.deleteWhere({
2434
+ collection: INTEGRATION_SETTINGS,
2435
+ filter: { where: {
2436
+ integration_id: integrationId,
2437
+ key
2438
+ } }
2348
2439
  });
2349
- await this.backend.tableInsert?.("integration_settings", {
2350
- integration_id: integrationId,
2351
- key,
2352
- value: s.value,
2353
- value_type: s.valueType
2440
+ await this.store.insert({
2441
+ collection: INTEGRATION_SETTINGS,
2442
+ record: {
2443
+ id: `${integrationId}:${key}`,
2444
+ data: {
2445
+ integration_id: integrationId,
2446
+ key,
2447
+ value: s.value,
2448
+ value_type: s.valueType
2449
+ }
2450
+ }
2354
2451
  });
2355
2452
  }
2356
2453
  async setIntegrationSettings(integrationId, settings) {
@@ -2359,16 +2456,21 @@ var IntegrationRegistry = class {
2359
2456
  async createDevice(input) {
2360
2457
  const id = nextDeviceId();
2361
2458
  const now = Math.floor(Date.now() / 1e3);
2362
- await this.backend.tableInsert?.("devices", {
2363
- id,
2364
- integration_id: input.integrationId,
2365
- stable_id: input.stableId,
2366
- type: input.type,
2367
- name: input.name,
2368
- enabled: input.enabled !== false ? 1 : 0,
2369
- info: JSON.stringify(input.info ?? {}),
2370
- created_at: now,
2371
- updated_at: now
2459
+ await this.store.insert({
2460
+ collection: DEVICES,
2461
+ record: {
2462
+ id,
2463
+ data: {
2464
+ integration_id: input.integrationId,
2465
+ stable_id: input.stableId,
2466
+ type: input.type,
2467
+ name: input.name,
2468
+ enabled: input.enabled !== false ? 1 : 0,
2469
+ info: input.info ?? {},
2470
+ created_at: now,
2471
+ updated_at: now
2472
+ }
2473
+ }
2372
2474
  });
2373
2475
  if (input.settings) await this.setDeviceSettings(id, input.settings);
2374
2476
  return {
@@ -2384,65 +2486,101 @@ var IntegrationRegistry = class {
2384
2486
  };
2385
2487
  }
2386
2488
  async getDevice(id) {
2387
- const row = await this.backend.tableGet?.("devices", { id });
2388
- return row ? this.mapDevice(row) : null;
2489
+ const rows = await this.store.query({
2490
+ collection: DEVICES,
2491
+ filter: {
2492
+ where: { id },
2493
+ limit: 1
2494
+ }
2495
+ });
2496
+ return rows[0] ? this.mapDevice(rows[0].data) : null;
2389
2497
  }
2390
2498
  async getDeviceByStableId(stableId) {
2391
- const row = await this.backend.tableGet?.("devices", { stable_id: stableId });
2392
- return row ? this.mapDevice(row) : null;
2499
+ const rows = await this.store.query({
2500
+ collection: DEVICES,
2501
+ filter: {
2502
+ where: { stable_id: stableId },
2503
+ limit: 1
2504
+ }
2505
+ });
2506
+ return rows[0] ? this.mapDevice(rows[0].data) : null;
2393
2507
  }
2394
2508
  async listDevices(integrationId) {
2395
- const options = integrationId ? {
2396
- where: { integration_id: integrationId },
2397
- orderBy: {
2398
- field: "created_at",
2399
- direction: "asc"
2509
+ return (await this.store.query({
2510
+ collection: DEVICES,
2511
+ filter: {
2512
+ ...integrationId ? { where: { integration_id: integrationId } } : {},
2513
+ orderBy: {
2514
+ field: "created_at",
2515
+ direction: "asc"
2516
+ }
2400
2517
  }
2401
- } : { orderBy: {
2402
- field: "created_at",
2403
- direction: "asc"
2404
- } };
2405
- return (await this.backend.tableQuery?.("devices", options) ?? []).map((r) => this.mapDevice(r));
2518
+ })).map((r) => this.mapDevice(r.data));
2406
2519
  }
2407
2520
  async listCameras() {
2408
- return (await this.backend.tableQuery?.("devices", {
2409
- where: { type: "camera" },
2410
- orderBy: {
2411
- field: "created_at",
2412
- direction: "asc"
2521
+ return (await this.store.query({
2522
+ collection: DEVICES,
2523
+ filter: {
2524
+ where: { type: "camera" },
2525
+ orderBy: {
2526
+ field: "created_at",
2527
+ direction: "asc"
2528
+ }
2413
2529
  }
2414
- }) ?? []).map((r) => this.mapDevice(r));
2530
+ })).map((r) => this.mapDevice(r.data));
2415
2531
  }
2416
2532
  async updateDevice(id, updates) {
2417
2533
  const updateRow = { updated_at: Math.floor(Date.now() / 1e3) };
2418
2534
  if (updates.name !== void 0) updateRow["name"] = updates.name;
2419
2535
  if (updates.enabled !== void 0) updateRow["enabled"] = updates.enabled ? 1 : 0;
2420
- if (updates.info !== void 0) updateRow["info"] = JSON.stringify(updates.info);
2421
- await this.backend.tableUpdate?.("devices", { id }, updateRow);
2536
+ if (updates.info !== void 0) updateRow["info"] = updates.info;
2537
+ await this.store.updateWhere({
2538
+ collection: DEVICES,
2539
+ filter: { where: { id } },
2540
+ data: updateRow
2541
+ });
2422
2542
  return this.getDevice(id);
2423
2543
  }
2424
2544
  async deleteDevice(id) {
2425
- await this.backend.tableDelete?.("device_settings_kv", { device_id: id });
2426
- await this.backend.tableDelete?.("devices", { id });
2545
+ await this.store.deleteWhere({
2546
+ collection: DEVICE_SETTINGS,
2547
+ filter: { where: { device_id: id } }
2548
+ });
2549
+ await this.store.deleteWhere({
2550
+ collection: DEVICES,
2551
+ filter: { where: { id } }
2552
+ });
2427
2553
  return true;
2428
2554
  }
2429
2555
  async getDeviceSettings(deviceId) {
2430
2556
  const result = {};
2431
- const rows = await this.backend.tableQuery?.("device_settings_kv", { where: { device_id: deviceId } }) ?? [];
2432
- for (const row of rows) result[String(row["key"])] = deserializeSetting(String(row["value"]), String(row["value_type"]));
2557
+ const rows = await this.store.query({
2558
+ collection: DEVICE_SETTINGS,
2559
+ filter: { where: { device_id: deviceId } }
2560
+ });
2561
+ for (const row of rows) result[String(row.data["key"])] = deserializeSetting(row.data["value"], String(row.data["value_type"]));
2433
2562
  return result;
2434
2563
  }
2435
2564
  async setDeviceSetting(deviceId, key, value) {
2436
2565
  const s = serializeSetting(value);
2437
- await this.backend.tableDelete?.("device_settings_kv", {
2438
- device_id: deviceId,
2439
- key
2566
+ await this.store.deleteWhere({
2567
+ collection: DEVICE_SETTINGS,
2568
+ filter: { where: {
2569
+ device_id: deviceId,
2570
+ key
2571
+ } }
2440
2572
  });
2441
- await this.backend.tableInsert?.("device_settings_kv", {
2442
- device_id: deviceId,
2443
- key,
2444
- value: s.value,
2445
- value_type: s.valueType
2573
+ await this.store.insert({
2574
+ collection: DEVICE_SETTINGS,
2575
+ record: {
2576
+ id: `${deviceId}:${key}`,
2577
+ data: {
2578
+ device_id: deviceId,
2579
+ key,
2580
+ value: s.value,
2581
+ value_type: s.valueType
2582
+ }
2583
+ }
2446
2584
  });
2447
2585
  }
2448
2586
  async setDeviceSettings(deviceId, settings) {
@@ -2454,7 +2592,7 @@ var IntegrationRegistry = class {
2454
2592
  addonId: String(row["addon_id"]),
2455
2593
  name: String(row["name"]),
2456
2594
  enabled: row["enabled"] === 1,
2457
- info: typeof row["info"] === "string" ? parseJsonObject(row["info"]) ?? {} : {},
2595
+ info: readInfo(row["info"]),
2458
2596
  createdAt: Number(row["created_at"]),
2459
2597
  updatedAt: Number(row["updated_at"])
2460
2598
  };
@@ -2467,7 +2605,7 @@ var IntegrationRegistry = class {
2467
2605
  type: String(row["type"]),
2468
2606
  name: String(row["name"]),
2469
2607
  enabled: row["enabled"] === 1,
2470
- info: typeof row["info"] === "string" ? parseJsonObject(row["info"]) ?? {} : {},
2608
+ info: readInfo(row["info"]),
2471
2609
  createdAt: Number(row["created_at"]),
2472
2610
  updatedAt: Number(row["updated_at"])
2473
2611
  };
@@ -3566,6 +3704,25 @@ function readPackageJson(pkgJsonPath) {
3566
3704
  camstackAddons: Array.isArray(addonsField) ? addonsField : null
3567
3705
  };
3568
3706
  }
3707
+ /**
3708
+ * Should the bootstrap SKIP seeding `packageName` into /data/addons?
3709
+ *
3710
+ * True only for the package the closure provides, and only when it actually
3711
+ * resolves. Both halves are load-bearing:
3712
+ *
3713
+ * - seeding a copy the closure already provides creates one that WINS over
3714
+ * the closure and is never refreshed (bootstrap is seed-only, and
3715
+ * `camstack deploy` of a framework package is rejected on an image hub), so
3716
+ * the node runs first-boot builtins forever while reporting the new
3717
+ * version;
3718
+ * - skipping it when it does NOT resolve is how a fresh deployment was once
3719
+ * made unbootable ("No addon provides required infrastructure capability
3720
+ * storage-provider"). That is why the guard is resolvability rather than a
3721
+ * blanket removal from the bootstrap list.
3722
+ */
3723
+ function shouldSkipClosureProvidedSeed(packageName, isResolvable) {
3724
+ return packageName === "@camstack/system" && isResolvable;
3725
+ }
3569
3726
  /** Minimal no-op logger for default parameter */
3570
3727
  var noopLogger = {
3571
3728
  debug() {},
@@ -3585,6 +3742,7 @@ var AddonInstaller = class AddonInstaller {
3585
3742
  workspaceDir;
3586
3743
  installSource;
3587
3744
  logger;
3745
+ isPackageResolvable;
3588
3746
  /**
3589
3747
  * Central manifest of installed addons. Tracks version + source +
3590
3748
  * timestamps + last-backup pointer per package, written atomically
@@ -3600,6 +3758,7 @@ var AddonInstaller = class AddonInstaller {
3600
3758
  this.workspaceDir = config.workspacePackagesDir;
3601
3759
  this.logger = logger;
3602
3760
  this.manifest = new AddonManifest(config.addonsDir);
3761
+ this.isPackageResolvable = config.isPackageResolvable;
3603
3762
  if (config.installSource) this.installSource = config.installSource;
3604
3763
  else this.installSource = "npm";
3605
3764
  }
@@ -3628,6 +3787,25 @@ var AddonInstaller = class AddonInstaller {
3628
3787
  * hardcoded lists — see design spec
3629
3788
  * docs/superpowers/specs/2026-05-20-docker-install-from-npm-design.md.
3630
3789
  */
3790
+ /**
3791
+ * Is `packageName` reachable through normal resolution — i.e. does the
3792
+ * running closure already provide it?
3793
+ *
3794
+ * Overridable via `AddonInstallerConfig.isPackageResolvable` so a spec can
3795
+ * exercise BOTH sides. That matters more than it looks: the branch that
3796
+ * still seeds is the one protecting a fresh deployment from an unbootable
3797
+ * node, and it is unreachable in a test process where `@camstack/system`
3798
+ * always resolves.
3799
+ */
3800
+ isResolvableFromClosure(packageName) {
3801
+ if (this.isPackageResolvable) return this.isPackageResolvable(packageName);
3802
+ try {
3803
+ __require.resolve(`${packageName}/package.json`);
3804
+ return true;
3805
+ } catch {
3806
+ return false;
3807
+ }
3808
+ }
3631
3809
  static deriveBootstrapList(metaPackageName) {
3632
3810
  const pkgJsonId = `${metaPackageName}/package.json`;
3633
3811
  let pkgPath;
@@ -3710,6 +3888,10 @@ var AddonInstaller = class AddonInstaller {
3710
3888
  this.logger.debug(`${packageName} — already installed, skipping`);
3711
3889
  continue;
3712
3890
  }
3891
+ if (shouldSkipClosureProvidedSeed(packageName, this.isResolvableFromClosure(packageName))) {
3892
+ this.logger.info(`${packageName} — provided by the server closure, not seeding a copy that would shadow it`);
3893
+ continue;
3894
+ }
3713
3895
  try {
3714
3896
  if (isLocal) {
3715
3897
  const pkgDir = this.findLocalPackage(packageName);