@camstack/system 1.2.29 → 1.2.30

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 (48) 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 +1 -1
  8. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +1 -1
  9. package/dist/builtins/console-logging/index.js +1 -1
  10. package/dist/builtins/console-logging/index.mjs +1 -1
  11. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  12. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  13. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  14. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  15. package/dist/builtins/hub-forwarder/index.js +1 -1
  16. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  17. package/dist/builtins/local-auth/local-auth.addon.js +1 -1
  18. package/dist/builtins/local-auth/local-auth.addon.mjs +1 -1
  19. package/dist/builtins/local-network/local-network.addon.js +1 -1
  20. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  21. package/dist/builtins/loki-logging/index.js +1 -1
  22. package/dist/builtins/loki-logging/index.mjs +1 -1
  23. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  24. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  25. package/dist/builtins/platform-probe/index.js +1 -1
  26. package/dist/builtins/platform-probe/index.mjs +1 -1
  27. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  28. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  29. package/dist/builtins/snapshot/index.js +1 -1
  30. package/dist/builtins/snapshot/index.mjs +1 -1
  31. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  32. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  33. package/dist/builtins/sqlite-storage/filter-compiler.d.ts +61 -0
  34. package/dist/builtins/sqlite-storage/sqlite-settings-backend.d.ts +43 -0
  35. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +115 -61
  36. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +115 -61
  37. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +3 -1
  38. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +3 -1
  39. package/dist/builtins/system-config/system-config.addon.js +1 -1
  40. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  41. package/dist/builtins/winston-logging/index.js +1 -1
  42. package/dist/builtins/winston-logging/index.mjs +1 -1
  43. package/dist/{dist-B7hOpr5i.mjs → dist-Dr_P0DOB.mjs} +81 -0
  44. package/dist/{dist-CPmFYLqq.js → dist-VIwdvws5.js} +81 -0
  45. package/dist/index.js +45 -1
  46. package/dist/index.mjs +45 -1
  47. package/dist/kernel/addon-installer.d.ts +38 -0
  48. package/package.json +1 -1
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { Mt as EventCategory, at as errMsg, ot as BaseAddon, p as addonPagesCapability } from "../../dist-B7hOpr5i.mjs";
1
+ import { Mt as EventCategory, at as errMsg, ot as BaseAddon, p as addonPagesCapability } from "../../dist-Dr_P0DOB.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  const require_settle_sources = require("../../settle-sources-Bhsy57y-.js");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { Mt as EventCategory, at as errMsg, h as addonWidgetsCapability, ot as BaseAddon } from "../../dist-B7hOpr5i.mjs";
1
+ import { Mt as EventCategory, at as errMsg, h as addonWidgetsCapability, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
2
2
  import { t as settleSourcesWithTimeout } from "../../settle-sources-CDtNC8ub.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  //#region src/builtins/alerts/prune-policy.ts
8
8
  /**
9
9
  * Ids older than `cutoffMs`, oldest first.
@@ -1,4 +1,4 @@
1
- import { Mt as EventCategory, at as errMsg, g as alertsCapability, n as AlertSchema, ot as BaseAddon, yt as createEvent } from "../../dist-B7hOpr5i.mjs";
1
+ import { Mt as EventCategory, at as errMsg, g as alertsCapability, n as AlertSchema, ot as BaseAddon, yt as createEvent } from "../../dist-Dr_P0DOB.mjs";
2
2
  //#region src/builtins/alerts/prune-policy.ts
3
3
  /**
4
4
  * Ids older than `cutoffMs`, oldest first.
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  let node_fs = require("node:fs");
8
8
  let node_fs$1 = require_chunk.__toESM(node_fs, 1);
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { Mt as EventCategory, ot as BaseAddon, y as backupCapability } from "../../dist-B7hOpr5i.mjs";
1
+ import { Mt as EventCategory, ot as BaseAddon, y as backupCapability } from "../../dist-Dr_P0DOB.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import Kt from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/console-logging/console-destination.ts
9
9
  var LEVEL_RANK = {
@@ -1,4 +1,4 @@
1
- import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-B7hOpr5i.mjs";
1
+ import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/console-logging/console-destination.ts
4
4
  var LEVEL_RANK = {
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  let _camstack_types_node = require("@camstack/types/node");
8
8
  let zod = require("zod");
9
9
  let node_crypto = require("node:crypto");
@@ -1,4 +1,4 @@
1
- import { C as deviceStateCapability, D as enumerateItemArrayFields, K as parseStreamParamsFormPatch, M as getByPath, Mt as EventCategory, O as enumerateSchemaFields, S as deviceManagerCapability, W as normalizeUnit, X as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as errMsg, b as buildStreamParamsConfigSchema, dt as DeviceRole, ft as DeviceType, ht as WELL_KNOWN_TAB_MAP, it as validateExpressionSource, jt as sleep, k as evaluateLinkExpression, l as STREAM_PROFILE_META, nt as toExpressionValue, o as DeviceStatusSchema, ot as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, ut as DeviceFeature, w as deviceStatusCapability, wt as isDeviceConfigCap } from "../../dist-B7hOpr5i.mjs";
1
+ import { C as deviceStateCapability, D as enumerateItemArrayFields, K as parseStreamParamsFormPatch, M as getByPath, Mt as EventCategory, O as enumerateSchemaFields, S as deviceManagerCapability, W as normalizeUnit, X as setByPath, _ as applyTransform, a as CAP_NAMES_WITH_STATUS, at as errMsg, b as buildStreamParamsConfigSchema, dt as DeviceRole, ft as DeviceType, ht as WELL_KNOWN_TAB_MAP, it as validateExpressionSource, jt as sleep, k as evaluateLinkExpression, l as STREAM_PROFILE_META, nt as toExpressionValue, o as DeviceStatusSchema, ot as BaseAddon, t as ALL_CAPABILITY_DEFINITIONS, ut as DeviceFeature, w as deviceStatusCapability, wt as isDeviceConfigCap } from "../../dist-Dr_P0DOB.mjs";
2
2
  import { canonicalDeviceFingerprint } from "@camstack/types/node";
3
3
  import { z } from "zod";
4
4
  import { randomUUID } from "node:crypto";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  //#region src/builtins/doorbell/trigger-engine.ts
8
8
  /**
9
9
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -1,4 +1,4 @@
1
- import { Mt as EventCategory, T as doorbellCapability, at as errMsg, ft as DeviceType, ot as BaseAddon, yt as createEvent } from "../../dist-B7hOpr5i.mjs";
1
+ import { Mt as EventCategory, T as doorbellCapability, at as errMsg, ft as DeviceType, ot as BaseAddon, yt as createEvent } from "../../dist-Dr_P0DOB.mjs";
2
2
  //#region src/builtins/doorbell/trigger-engine.ts
3
3
  /**
4
4
  * Pure trigger logic for the virtual-doorbell builtin — edge detection over
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
9
9
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -1,4 +1,4 @@
1
- import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-B7hOpr5i.mjs";
1
+ import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  //#region src/builtins/hub-forwarder/hub-forwarder-destination.ts
4
4
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  node_crypto = require_chunk.__toESM(node_crypto);
9
9
  let crypto$1 = require("crypto");
@@ -1,5 +1,5 @@
1
1
  import { i as __require, o as __toESM, t as __commonJSMin } from "../../chunk-CNf5ZN-e.mjs";
2
- import { f as UserRecordSchema, ot as BaseAddon, r as ApiKeyRecordSchema, rt as userManagementCapability, u as ScopedTokenSchema, v as authProviderCapability } from "../../dist-B7hOpr5i.mjs";
2
+ import { f as UserRecordSchema, ot as BaseAddon, r as ApiKeyRecordSchema, rt as userManagementCapability, u as ScopedTokenSchema, v as authProviderCapability } from "../../dist-Dr_P0DOB.mjs";
3
3
  import * as crypto$1 from "node:crypto";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import nodeCrypto from "crypto";
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_chunk = require("../../chunk-Cek0wNdY.js");
3
- const require_dist = require("../../dist-CPmFYLqq.js");
3
+ const require_dist = require("../../dist-VIwdvws5.js");
4
4
  let node_os = require("node:os");
5
5
  node_os = require_chunk.__toESM(node_os);
6
6
  //#region src/builtins/local-network/local-network.addon.ts
@@ -1,4 +1,4 @@
1
- import { Mt as EventCategory, ot as BaseAddon, z as localNetworkCapability } from "../../dist-B7hOpr5i.mjs";
1
+ import { Mt as EventCategory, ot as BaseAddon, z as localNetworkCapability } from "../../dist-Dr_P0DOB.mjs";
2
2
  import * as os from "node:os";
3
3
  //#region src/builtins/local-network/local-network.addon.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  //#region src/builtins/loki-logging/loki-payload.ts
8
8
  /**
9
9
  * Loki rejects a label name that is not a valid Prometheus label
@@ -1,4 +1,4 @@
1
- import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-B7hOpr5i.mjs";
1
+ import { B as logDestinationCapability, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
2
2
  //#region src/builtins/loki-logging/loki-payload.ts
3
3
  /**
4
4
  * Loki rejects a label name that is not a valid Prometheus label
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,4 +1,4 @@
1
- import { Mt as EventCategory, U as metricsProviderCapability, ot as BaseAddon, yt as createEvent } from "../../dist-B7hOpr5i.mjs";
1
+ import { Mt as EventCategory, U as metricsProviderCapability, ot as BaseAddon, yt as createEvent } from "../../dist-Dr_P0DOB.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import { execFile, execFileSync } from "node:child_process";
4
4
  import { promisify } from "node:util";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_child_process = require("node:child_process");
@@ -1,5 +1,5 @@
1
1
  import { o as __toESM } from "../../chunk-CNf5ZN-e.mjs";
2
- import { E as enumerateInferenceDevices, Mt as EventCategory, Y as scoreRuntimes, at as errMsg, ot as BaseAddon, q as platformProbeCapability, xt as emitReadiness } from "../../dist-B7hOpr5i.mjs";
2
+ import { E as enumerateInferenceDevices, Mt as EventCategory, Y as scoreRuntimes, at as errMsg, ot as BaseAddon, q as platformProbeCapability, xt as emitReadiness } from "../../dist-Dr_P0DOB.mjs";
3
3
  import * as fs from "node:fs";
4
4
  import { execFile } from "node:child_process";
5
5
  import { promisify } from "node:util";
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
8
8
  /**
9
9
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -1,4 +1,4 @@
1
- import { Mt as EventCategory, ot as BaseAddon } from "../../dist-B7hOpr5i.mjs";
1
+ import { Mt as EventCategory, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
2
2
  //#region src/builtins/remote-access-orchestrator/enabled-providers-reconcile.ts
3
3
  /**
4
4
  * Reconcile the durable `enabledProviders` set against authoritative
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  let node_child_process = require("node:child_process");
8
8
  //#region src/builtins/snapshot/snapshot-coalescing.ts
9
9
  /**
@@ -1,4 +1,4 @@
1
- import { Q as snapshotCapability, Tt as nodePin, at as errMsg, ft as DeviceType, i as BatteryStatusSchema, ot as BaseAddon, tt as streamQualityLabel, ut as DeviceFeature } from "../../dist-B7hOpr5i.mjs";
1
+ import { Q as snapshotCapability, Tt as nodePin, at as errMsg, ft as DeviceType, i as BatteryStatusSchema, ot as BaseAddon, tt as streamQualityLabel, ut as DeviceFeature } from "../../dist-Dr_P0DOB.mjs";
2
2
  import { execFile } from "node:child_process";
3
3
  //#region src/builtins/snapshot/snapshot-coalescing.ts
4
4
  /**
@@ -3,7 +3,7 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  let node_fs = require("node:fs");
8
8
  node_fs = require_chunk.__toESM(node_fs);
9
9
  let node_path = require("node:path");
@@ -1,4 +1,4 @@
1
- import { et as storageProviderCapability, j as filesystemBrowseCapability, ot as BaseAddon } from "../../dist-B7hOpr5i.mjs";
1
+ import { et as storageProviderCapability, j as filesystemBrowseCapability, ot as BaseAddon } from "../../dist-Dr_P0DOB.mjs";
2
2
  import * as fs from "node:fs";
3
3
  import * as path$1 from "node:path";
4
4
  import { basename, dirname, join, resolve, sep } from "node:path";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * One filter compiler for both the read and the bulk-mutation paths.
3
+ *
4
+ * It exists because those two paths must NOT behave the same way, and until
5
+ * now the only compiler lived inline in `query` — where being forgiving is
6
+ * correct. Reusing it verbatim for `deleteWhere` would have shipped two
7
+ * data-loss shapes:
8
+ *
9
+ * - **A dropped predicate.** A field that is not a column of a structured
10
+ * table is skipped, so `{ deviceId: 615, kind: 'motion' }` can compile to
11
+ * `WHERE "deviceId" = ?` — on a SELECT that returns extra rows, on a DELETE
12
+ * it removes rows the caller meant to keep.
13
+ * - **No predicate at all.** An empty filter compiles to an empty WHERE,
14
+ * which on a DELETE is the entire collection.
15
+ *
16
+ * `select` mode keeps the historical behaviour, because every existing caller
17
+ * was written against it. `mutate` mode refuses both, before any SQL exists.
18
+ * Emptying a collection is a legitimate thing to want, and it should be a
19
+ * differently-named operation rather than a filter that happens to be empty.
20
+ */
21
+ /** The part of a declared collection this compiler needs. */
22
+ export interface CollectionShape {
23
+ readonly primaryKey: string;
24
+ readonly columns: ReadonlySet<string>;
25
+ /**
26
+ * The single JSON blob column for a KV-shaped collection (fields inside it
27
+ * are reachable via `json_extract`), or `null` for a structured table whose
28
+ * fields are real columns.
29
+ */
30
+ readonly kvBlobColumn: string | null;
31
+ }
32
+ export type FilterMode = 'select' | 'mutate';
33
+ /** Marshals a JS value to what the driver should bind. */
34
+ export type SerializeValue = (value: unknown) => unknown;
35
+ export interface QueryFilterInput {
36
+ readonly where?: Readonly<Record<string, unknown>>;
37
+ readonly whereIn?: Readonly<Record<string, readonly unknown[]>>;
38
+ readonly whereBetween?: Readonly<Record<string, readonly [unknown, unknown]>>;
39
+ }
40
+ /**
41
+ * The predicate-only filter a bulk mutation accepts — no ordering, no limit.
42
+ * Structurally the mutation half of {@link QueryFilterInput}.
43
+ */
44
+ export type MutationFilterInput = QueryFilterInput;
45
+ export interface CompiledFilter {
46
+ /** Either `''` or a leading-space ` WHERE …` clause, ready to concatenate. */
47
+ readonly whereSql: string;
48
+ readonly params: readonly unknown[];
49
+ }
50
+ /** Thrown by `mutate` mode. Distinct type so a caller can map it to a 400. */
51
+ export declare class UnsafeFilterError extends Error {
52
+ constructor(message: string);
53
+ }
54
+ /**
55
+ * SQL expression for `field` on this collection, or `null` when it cannot be
56
+ * expressed: a real column → `"field"`; a KV blob field → `json_extract`;
57
+ * anything else on a structured table → unresolvable. Exported because
58
+ * `histogram` needs the same resolution for its bucketed column.
59
+ */
60
+ export declare function fieldExprFor(field: string, shape: CollectionShape): string | null;
61
+ export declare function compileFilter(filter: QueryFilterInput | undefined, shape: CollectionShape, mode: FilterMode, serialize: SerializeValue): CompiledFilter;
@@ -1,5 +1,25 @@
1
1
  import { default as Database } from 'better-sqlite3';
2
2
  import { DataStoreEngineInfo, ISettingsBackend, SettingsRecord, TableSchema, TableQueryOptions, SettingsGetInput, SettingsSetInput, SettingsQueryInput, SettingsInsertInput, SettingsUpdateInput, SettingsDeleteInput, SettingsCountInput, SettingsIsEmptyInput, SettingsHistogramInput, HistogramBucket, CollectionColumn, CollectionIndex } from '@camstack/types';
3
+ import { MutationFilterInput } from './filter-compiler.js';
4
+ /** Input for {@link SqliteSettingsBackend.deleteWhere}. */
5
+ interface SettingsDeleteWhereInput {
6
+ readonly namespace?: string;
7
+ readonly collection: string;
8
+ readonly filter: MutationFilterInput;
9
+ }
10
+ interface SettingsDeleteWhereResult {
11
+ readonly deleted: number;
12
+ }
13
+ /** Input for {@link SqliteSettingsBackend.updateWhere}. */
14
+ interface SettingsUpdateWhereInput {
15
+ readonly namespace?: string;
16
+ readonly collection: string;
17
+ readonly filter: MutationFilterInput;
18
+ readonly data: Readonly<Record<string, unknown>>;
19
+ }
20
+ interface SettingsUpdateWhereResult {
21
+ readonly updated: number;
22
+ }
3
23
  /**
4
24
  * SQLite implementation of ISettingsBackend.
5
25
  *
@@ -51,6 +71,23 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
51
71
  insert<T extends object = Record<string, unknown>>({ namespace, collection, record, }: SettingsInsertInput<T>): Promise<void>;
52
72
  update({ namespace, collection, id, data }: SettingsUpdateInput): Promise<void>;
53
73
  delete({ namespace, collection, key }: SettingsDeleteInput): Promise<void>;
74
+ /**
75
+ * Bulk delete. One statement, whatever the row count — this is what every
76
+ * retention path in the system was hand-rolling as a select-then-delete
77
+ * loop. The filter is compiled in `mutate` mode, so an unresolvable
78
+ * predicate or an empty one throws before any SQL exists.
79
+ */
80
+ deleteWhere({ namespace, collection, filter, }: SettingsDeleteWhereInput): Promise<SettingsDeleteWhereResult>;
81
+ /**
82
+ * Bulk update. Same filter contract as {@link deleteWhere}.
83
+ *
84
+ * Every key of `data` must be a REAL column. A KV-shaped collection keeps
85
+ * its fields inside a JSON blob, and setting one would need `json_set` with
86
+ * read-modify-write semantics this method does not have — so it refuses
87
+ * rather than writing a column that does not exist, which is what the
88
+ * uncapped `tableUpdate` would have done.
89
+ */
90
+ updateWhere({ namespace, collection, filter, data, }: SettingsUpdateWhereInput): Promise<SettingsUpdateWhereResult>;
54
91
  count({ namespace, collection, filter }: SettingsCountInput): Promise<number>;
55
92
  histogram({ namespace, collection, field, bucketSize, origin, filter, }: SettingsHistogramInput): Promise<readonly HistogramBucket[]>;
56
93
  isEmpty({ namespace, collection }: SettingsIsEmptyInput): Promise<boolean>;
@@ -88,6 +125,12 @@ export declare class SqliteSettingsBackend implements ISettingsBackend {
88
125
  private getAllScoped;
89
126
  private setScopedKey;
90
127
  private scopedName;
128
+ /**
129
+ * Project a declared collection onto what {@link compileFilter} needs. A
130
+ * collection whose only column is `data` is KV-shaped: its fields live
131
+ * inside that blob and are reached with `json_extract`.
132
+ */
133
+ private shapeOf;
91
134
  declareCollection(input: {
92
135
  namespace?: string;
93
136
  collection: string;
@@ -3,10 +3,66 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_chunk = require("../../chunk-Cek0wNdY.js");
6
- const require_dist = require("../../dist-CPmFYLqq.js");
6
+ const require_dist = require("../../dist-VIwdvws5.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let better_sqlite3 = require("better-sqlite3");
9
9
  better_sqlite3 = require_chunk.__toESM(better_sqlite3);
10
+ //#region src/builtins/sqlite-storage/filter-compiler.ts
11
+ /** Thrown by `mutate` mode. Distinct type so a caller can map it to a 400. */
12
+ var UnsafeFilterError = class extends Error {
13
+ constructor(message) {
14
+ super(message);
15
+ this.name = "UnsafeFilterError";
16
+ }
17
+ };
18
+ /**
19
+ * SQL expression for `field` on this collection, or `null` when it cannot be
20
+ * expressed: a real column → `"field"`; a KV blob field → `json_extract`;
21
+ * anything else on a structured table → unresolvable. Exported because
22
+ * `histogram` needs the same resolution for its bucketed column.
23
+ */
24
+ function fieldExprFor(field, shape) {
25
+ if (field === shape.primaryKey || shape.columns.has(field)) return `"${field}"`;
26
+ if (shape.kvBlobColumn !== null) return `json_extract("${shape.kvBlobColumn}", '$.${field}')`;
27
+ return null;
28
+ }
29
+ function compileFilter(filter, shape, mode, serialize) {
30
+ const clauses = [];
31
+ const params = [];
32
+ const resolve = (field) => {
33
+ const expr = fieldExprFor(field, shape);
34
+ if (expr === null && mode === "mutate") throw new UnsafeFilterError(`filter refers to "${field}", which this collection cannot express — refusing to run a bulk mutation with a dropped predicate`);
35
+ return expr;
36
+ };
37
+ for (const [field, value] of Object.entries(filter?.where ?? {})) {
38
+ const expr = resolve(field);
39
+ if (expr === null) continue;
40
+ clauses.push(`${expr} = ?`);
41
+ params.push(serialize(value));
42
+ }
43
+ for (const [field, values] of Object.entries(filter?.whereIn ?? {})) {
44
+ const expr = resolve(field);
45
+ if (expr === null) continue;
46
+ if (values.length === 0) {
47
+ if (mode === "mutate") throw new UnsafeFilterError(`filter has an empty whereIn list for "${field}" — it matches nothing, which is more likely a caller bug than an intent`);
48
+ continue;
49
+ }
50
+ clauses.push(`${expr} IN (${values.map(() => "?").join(", ")})`);
51
+ for (const v of values) params.push(serialize(v));
52
+ }
53
+ for (const [field, [low, high]] of Object.entries(filter?.whereBetween ?? {})) {
54
+ const expr = resolve(field);
55
+ if (expr === null) continue;
56
+ clauses.push(`${expr} BETWEEN ? AND ?`);
57
+ params.push(serialize(low), serialize(high));
58
+ }
59
+ if (clauses.length === 0 && mode === "mutate") throw new UnsafeFilterError("filter compiled to no predicate — that would affect every row in the collection; use the explicit clear operation if that is the intent");
60
+ return {
61
+ whereSql: clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "",
62
+ params
63
+ };
64
+ }
65
+ //#endregion
10
66
  //#region src/builtins/sqlite-storage/sqlite-settings-backend.ts
11
67
  function parseRowData(raw) {
12
68
  return require_dist.asJsonObject(require_dist.parseJsonUnknown(raw)) ?? {};
@@ -189,6 +245,41 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
189
245
  const decl = this.requireDeclared(scoped);
190
246
  await this.tableDelete(scoped, { [decl.primaryKey]: key });
191
247
  }
248
+ /**
249
+ * Bulk delete. One statement, whatever the row count — this is what every
250
+ * retention path in the system was hand-rolling as a select-then-delete
251
+ * loop. The filter is compiled in `mutate` mode, so an unresolvable
252
+ * predicate or an empty one throws before any SQL exists.
253
+ */
254
+ async deleteWhere({ namespace, collection, filter }) {
255
+ const scoped = this.scopedName(namespace, collection);
256
+ const decl = this.requireDeclared(scoped);
257
+ const { whereSql, params } = compileFilter(filter, this.shapeOf(decl), "mutate", (v) => this.serializeColumnValue(v));
258
+ return { deleted: this.getDb().prepare(`DELETE FROM "${scoped}"${whereSql}`).run(...params).changes };
259
+ }
260
+ /**
261
+ * Bulk update. Same filter contract as {@link deleteWhere}.
262
+ *
263
+ * Every key of `data` must be a REAL column. A KV-shaped collection keeps
264
+ * its fields inside a JSON blob, and setting one would need `json_set` with
265
+ * read-modify-write semantics this method does not have — so it refuses
266
+ * rather than writing a column that does not exist, which is what the
267
+ * uncapped `tableUpdate` would have done.
268
+ */
269
+ async updateWhere({ namespace, collection, filter, data }) {
270
+ const scoped = this.scopedName(namespace, collection);
271
+ const decl = this.requireDeclared(scoped);
272
+ const setClauses = [];
273
+ const setValues = [];
274
+ for (const [key, value] of Object.entries(data)) {
275
+ if (key !== decl.primaryKey && !decl.columns.has(key)) throw new UnsafeFilterError(`updateWhere cannot set "${key}" on "${scoped}" — it is not a column of this collection`);
276
+ setClauses.push(`"${key}" = ?`);
277
+ setValues.push(this.serializeColumnValue(value));
278
+ }
279
+ if (setClauses.length === 0) throw new UnsafeFilterError("updateWhere was given no fields to set");
280
+ const { whereSql, params } = compileFilter(filter, this.shapeOf(decl), "mutate", (v) => this.serializeColumnValue(v));
281
+ return { updated: this.getDb().prepare(`UPDATE "${scoped}" SET ${setClauses.join(", ")}${whereSql}`).run(...setValues, ...params).changes };
282
+ }
192
283
  async count({ namespace, collection, filter }) {
193
284
  const scoped = this.scopedName(namespace, collection);
194
285
  const decl = this.requireDeclared(scoped);
@@ -216,30 +307,10 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
216
307
  async histogram({ namespace, collection, field, bucketSize, origin, filter }) {
217
308
  const scoped = this.scopedName(namespace, collection);
218
309
  const decl = this.requireDeclared(scoped);
219
- const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
220
- const isColumn = (f) => f === decl.primaryKey || decl.columns.has(f);
221
- const fieldExpr = (f) => {
222
- if (isColumn(f)) return `"${f}"`;
223
- if (isKvShape) return `json_extract("data", '$.${f}')`;
224
- return "";
225
- };
226
- const col = fieldExpr(field);
227
- if (!col) return [];
228
- const params = [];
229
- const clauses = [];
230
- if (filter?.where) for (const [f, value] of Object.entries(filter.where)) {
231
- const expr = fieldExpr(f);
232
- if (!expr) continue;
233
- clauses.push(`${expr} = ?`);
234
- params.push(this.serializeColumnValue(value));
235
- }
236
- if (filter?.whereBetween) for (const [f, [lo, hi]] of Object.entries(filter.whereBetween)) {
237
- const expr = fieldExpr(f);
238
- if (!expr) continue;
239
- clauses.push(`${expr} BETWEEN ? AND ?`);
240
- params.push(this.serializeColumnValue(lo), this.serializeColumnValue(hi));
241
- }
242
- const where = clauses.length > 0 ? ` WHERE ${clauses.join(" AND ")}` : "";
310
+ const shape = this.shapeOf(decl);
311
+ const col = fieldExprFor(field, shape);
312
+ if (col === null) return [];
313
+ const { whereSql: where, params } = compileFilter(filter, shape, "select", (v) => this.serializeColumnValue(v));
243
314
  const sql = `SELECT ${`CAST((${col} - ?) / ? AS INTEGER)`} AS bucket, COUNT(*) AS count FROM "${scoped}"${where} GROUP BY bucket ORDER BY bucket`;
244
315
  return this.getDb().prepare(sql).all(origin, bucketSize, ...params).map((r) => ({
245
316
  bucket: r.bucket,
@@ -255,43 +326,13 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
255
326
  const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
256
327
  let sql = `SELECT ${[`"${decl.primaryKey}"`, ...[...decl.columns].map((c) => `"${c}"`)].join(", ")} FROM "${table}"`;
257
328
  const params = [];
258
- const whereClauses = [];
259
- const isColumn = (f) => f === decl.primaryKey || decl.columns.has(f);
260
- /**
261
- * SQL expression for `field` on this table.
262
- * - real column → `"field"`
263
- * - KV blob field (e.g. `username` inside `data`) → `json_extract`
264
- * - structured table + non-column → empty string (filter dropped,
265
- * matches legacy structured-table behaviour)
266
- */
267
- const fieldExpr = (f) => {
268
- if (isColumn(f)) return `"${f}"`;
269
- if (isKvShape) return `json_extract("data", '$.${f}')`;
270
- return "";
271
- };
272
- if (filter?.where) for (const [field, value] of Object.entries(filter.where)) {
273
- const expr = fieldExpr(field);
274
- if (!expr) continue;
275
- whereClauses.push(`${expr} = ?`);
276
- params.push(this.serializeColumnValue(value));
277
- }
278
- if (filter?.whereIn) for (const [field, values] of Object.entries(filter.whereIn)) {
279
- const expr = fieldExpr(field);
280
- if (!expr) continue;
281
- const placeholders = values.map(() => "?").join(", ");
282
- whereClauses.push(`${expr} IN (${placeholders})`);
283
- for (const v of values) params.push(this.serializeColumnValue(v));
284
- }
285
- if (filter?.whereBetween) for (const [field, [low, high]] of Object.entries(filter.whereBetween)) {
286
- const expr = fieldExpr(field);
287
- if (!expr) continue;
288
- whereClauses.push(`${expr} BETWEEN ? AND ?`);
289
- params.push(this.serializeColumnValue(low), this.serializeColumnValue(high));
290
- }
291
- if (whereClauses.length > 0) sql += ` WHERE ${whereClauses.join(" AND ")}`;
329
+ const shape = this.shapeOf(decl);
330
+ const compiled = compileFilter(filter, shape, "select", (v) => this.serializeColumnValue(v));
331
+ sql += compiled.whereSql;
332
+ params.push(...compiled.params);
292
333
  if (filter?.orderBy) {
293
- const expr = fieldExpr(filter.orderBy.field);
294
- if (expr) {
334
+ const expr = fieldExprFor(filter.orderBy.field, shape);
335
+ if (expr !== null) {
295
336
  const dir = filter.orderBy.direction === "desc" ? "DESC" : "ASC";
296
337
  sql += ` ORDER BY ${expr} ${dir}`;
297
338
  }
@@ -467,6 +508,19 @@ var SqliteSettingsBackend = class SqliteSettingsBackend {
467
508
  scopedName(namespace, collection) {
468
509
  return namespace ? `${namespace}:${collection}` : collection;
469
510
  }
511
+ /**
512
+ * Project a declared collection onto what {@link compileFilter} needs. A
513
+ * collection whose only column is `data` is KV-shaped: its fields live
514
+ * inside that blob and are reached with `json_extract`.
515
+ */
516
+ shapeOf(decl) {
517
+ const isKvShape = decl.columns.size === 1 && decl.columns.has("data");
518
+ return {
519
+ primaryKey: decl.primaryKey,
520
+ columns: decl.columns,
521
+ kvBlobColumn: isKvShape ? "data" : null
522
+ };
523
+ }
470
524
  async declareCollection(input) {
471
525
  const table = this.scopedName(input.namespace, input.collection);
472
526
  if (this.declaredCollections.has(table)) return;