@camstack/system 1.2.62 → 1.2.64

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 (90) hide show
  1. package/dist/addon-runner.js +1 -1
  2. package/dist/addon-runner.mjs +1 -1
  3. package/dist/addon-utils.js +1 -1
  4. package/dist/addon-utils.mjs +1 -1
  5. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  8. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  9. package/dist/builtins/alerts/alerts.addon.js +1 -1
  10. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +3 -3
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  13. package/dist/builtins/console-logging/index.js +1 -1
  14. package/dist/builtins/console-logging/index.mjs +1 -1
  15. package/dist/builtins/core-blocks/block-package.d.ts +9 -27
  16. package/dist/builtins/core-blocks/block-supervisor.d.ts +15 -0
  17. package/dist/builtins/core-blocks/core-blocks.addon.js +91 -24
  18. package/dist/builtins/core-blocks/core-blocks.addon.mjs +91 -25
  19. package/dist/builtins/device-manager/device-bindings-store.d.ts +28 -5
  20. package/dist/builtins/device-manager/device-linked-devices.d.ts +2 -2
  21. package/dist/builtins/device-manager/device-manager.addon.d.ts +3 -45
  22. package/dist/builtins/device-manager/device-manager.addon.js +123 -884
  23. package/dist/builtins/device-manager/device-manager.addon.mjs +123 -884
  24. package/dist/builtins/device-manager/device-meta-actions.d.ts +0 -8
  25. package/dist/builtins/device-manager/device-meta-store.d.ts +1 -28
  26. package/dist/builtins/device-manager/device-meta-types.d.ts +2 -6
  27. package/dist/builtins/device-manager/device-projection.d.ts +1 -2
  28. package/dist/builtins/device-manager/device-provider-context.d.ts +2 -13
  29. package/dist/builtins/device-manager/device-queries.d.ts +1 -1
  30. package/dist/builtins/device-manager/device-state-mirror.d.ts +8 -49
  31. package/dist/builtins/device-manager/device-sync-resolvers.d.ts +0 -11
  32. package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
  33. package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
  34. package/dist/builtins/hub-forwarder/index.js +1 -1
  35. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  36. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  37. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  38. package/dist/builtins/local-auth/homeassistant-integration.d.ts +7 -0
  39. package/dist/builtins/local-auth/index.d.ts +1 -0
  40. package/dist/builtins/local-auth/index.js +1 -0
  41. package/dist/builtins/local-auth/index.mjs +2 -2
  42. package/dist/builtins/local-auth/local-auth.addon.js +561 -497
  43. package/dist/builtins/local-auth/local-auth.addon.mjs +580 -517
  44. package/dist/builtins/local-auth/oauth-grants.d.ts +11 -0
  45. package/dist/builtins/local-auth/oauth-session-manager.d.ts +1 -1
  46. package/dist/builtins/local-network/local-network.addon.js +1 -1
  47. package/dist/builtins/local-network/local-network.addon.mjs +1 -1
  48. package/dist/builtins/loki-logging/index.js +1 -1
  49. package/dist/builtins/loki-logging/index.mjs +1 -1
  50. package/dist/builtins/native-metrics/native-metrics.addon.js +2 -2
  51. package/dist/builtins/native-metrics/native-metrics.addon.mjs +2 -2
  52. package/dist/builtins/platform-probe/index.js +2 -2
  53. package/dist/builtins/platform-probe/index.mjs +2 -2
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  55. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  56. package/dist/builtins/snapshot/index.js +319 -20
  57. package/dist/builtins/snapshot/index.mjs +319 -20
  58. package/dist/builtins/snapshot/snapshot-keep-warm.d.ts +86 -0
  59. package/dist/builtins/snapshot/snapshot-media-handler.d.ts +48 -1
  60. package/dist/builtins/snapshot/snapshot.addon.d.ts +65 -1
  61. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  62. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  63. package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +15 -0
  64. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +73 -7
  65. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +73 -7
  66. package/dist/builtins/sqlite-storage/vector-index-vec.d.ts +19 -0
  67. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  68. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  69. package/dist/builtins/system-config/system-config.addon.js +1 -1
  70. package/dist/builtins/system-config/system-config.addon.mjs +1 -1
  71. package/dist/builtins/winston-logging/index.js +1 -1
  72. package/dist/builtins/winston-logging/index.mjs +1 -1
  73. package/dist/{dist-DnKpFaP1.mjs → dist-CMb7aKS0.mjs} +1227 -1330
  74. package/dist/{dist-DgPUarcs.js → dist-DP3nHlNM.js} +1251 -1366
  75. package/dist/http/asset-compression.d.ts +93 -0
  76. package/dist/index.d.ts +1 -1
  77. package/dist/index.js +64 -9
  78. package/dist/index.mjs +65 -11
  79. package/dist/kernel/heap-watch.d.ts +55 -0
  80. package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -2
  81. package/dist/{manifest-python-deps-d4acYXVV.js → manifest-python-deps-BE6l8Y4W.js} +21 -15
  82. package/dist/{manifest-python-deps-CX5tGRqf.mjs → manifest-python-deps-Dz1ryHIN.mjs} +21 -15
  83. package/dist/{model-download-service-D8B-4ktF.mjs → model-download-service-CTBHzORJ.mjs} +220 -2
  84. package/dist/{model-download-service-Cpc6SWzV.js → model-download-service-Caj1kaSi.js} +220 -2
  85. package/package.json +5 -1
  86. package/dist/builtins/device-manager/device-link-cycle.d.ts +0 -18
  87. package/dist/builtins/device-manager/device-link-overlay.d.ts +0 -33
  88. package/dist/builtins/device-manager/device-link-resolver.d.ts +0 -44
  89. package/dist/main-CVLq8IzA.js +0 -2356
  90. package/dist/main-CzH19GkU.mjs +0 -2353
@@ -1,4 +1,5 @@
1
1
  import { AddonApi, ProfileSlot, ProviderRegistration, RtspRestreamEntry, BaseAddon } from '@camstack/types';
2
+ import { JpegResizer } from './snapshot-resize.js';
2
3
  interface SnapshotAddonConfig {
3
4
  /**
4
5
  * Last-resort cache age (ms): if every live capture path fails
@@ -60,7 +61,26 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
60
61
  * strictly-additive delivery path). Disposed on shutdown.
61
62
  */
62
63
  private mediaDataPlane;
63
- constructor();
64
+ /**
65
+ * Who is being looked at, inferred from data-plane traffic. The keep-warm
66
+ * loop's entire bound — see `snapshot-keep-warm.ts`.
67
+ */
68
+ private readonly subscriptions;
69
+ /** Chained warm-tick timer (never `setInterval` — see `scheduleWarmTick`). */
70
+ private warmTimer;
71
+ /** Set while a warm tick is running so ticks can never stack. */
72
+ private warmTickRunning;
73
+ /** Flipped on shutdown so an in-flight tick does not reschedule itself. */
74
+ private stopped;
75
+ /**
76
+ * How a full frame becomes a card-sized one. Defaults to the real ffmpeg
77
+ * resize; injectable so the wrapper's own logic is testable without spawning a
78
+ * process for every fixture. `JpegResizer` was declared for exactly this and
79
+ * had no injector — which is why the resize-failure path had never been
80
+ * exercised anywhere but production.
81
+ */
82
+ private readonly resize;
83
+ constructor(resize?: JpegResizer);
64
84
  protected onInitialize(): Promise<ProviderRegistration[]>;
65
85
  /**
66
86
  * Serve the per-device snapshot image endpoint on the addon data-plane
@@ -81,6 +101,50 @@ export declare class SnapshotAddon extends BaseAddon<SnapshotAddonConfig> {
81
101
  * effective per-device `maxAgeS` for `Cache-Control`. Null → 404.
82
102
  */
83
103
  private resolveSnapshotMedia;
104
+ /**
105
+ * Cache-only freshness peek for the data-plane's conditional-GET fast path.
106
+ * Never captures, never resizes, never dials anything: a cached frame that is
107
+ * still inside its per-device max-age, or null.
108
+ *
109
+ * Returning null for a STALE entry is the important half. A 304 there would
110
+ * pin the tile to the old frame for as long as the client kept revalidating,
111
+ * which is the exact staleness this change exists to remove.
112
+ */
113
+ private peekFreshFrame;
114
+ /**
115
+ * Schedule the next warm tick. A CHAINED timer, not `setInterval`: the next
116
+ * tick is armed only after the previous one settles, so a fleet that is slower
117
+ * than the interval degrades to "as fast as it can" instead of stacking ticks
118
+ * on top of each other and exhausting the ffmpeg pool.
119
+ *
120
+ * `unref` so the loop never holds the process open.
121
+ */
122
+ private scheduleWarmTick;
123
+ /**
124
+ * Refresh the cache for every camera somebody is currently looking at.
125
+ *
126
+ * Deliberately NOT a blind poll of the whole fleet: with nobody watching this
127
+ * walks an empty list and costs nothing, which is the entire difference
128
+ * between this and a background poller.
129
+ *
130
+ * Battery cameras need no special case here and must not get one. A warm tick
131
+ * is a plain non-forced `getSnapshot`, so it meets the two gates that already
132
+ * exist — the 3600 s battery max-age (which makes the tick a cache hit) and
133
+ * the sleeping-battery gate (which refuses to capture without `force`). A
134
+ * second battery check here would be a rule that disagrees with the authority
135
+ * that already owns it.
136
+ */
137
+ private runWarmTick;
138
+ /**
139
+ * Warm one (device, stream): refresh the frame, then pre-derive the widths
140
+ * that were actually requested.
141
+ *
142
+ * Deriving the variants matters as much as the capture. Warming only the full
143
+ * frame would leave every grid tile still paying an ffmpeg resize on the
144
+ * request path — the cache would be fresh and the tile would still be slow.
145
+ */
146
+ private warmOne;
147
+ private warmOneUnbounded;
84
148
  /**
85
149
  * The frame at a card-sized width, derived once per (device, stream, width)
86
150
  * per capture.
@@ -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-DgPUarcs.js");
6
+ const require_dist = require("../../dist-DP3nHlNM.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  node_fs = require_chunk.__toESM(node_fs);
@@ -1,4 +1,4 @@
1
- import { F as filesystemBrowseCapability, at as storageProviderCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
1
+ import { I as filesystemBrowseCapability, at as storageProviderCapability, dt as BaseAddon } from "../../dist-CMb7aKS0.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import * as fs from "node:fs";
4
4
  import * as path$1 from "node:path";
@@ -15,6 +15,14 @@ export interface RetiredKeySpec {
15
15
  * `perNode: true` schema field.
16
16
  */
17
17
  readonly perNodeKeys: readonly string[];
18
+ /**
19
+ * Keys retired one level DOWN, inside each object value of a map-shaped row.
20
+ *
21
+ * `device-manager`'s `deviceMeta` row is `{ [deviceId]: PersistedDeviceMeta }`:
22
+ * the dead key is not a key of the row, it is a key of each device's meta.
23
+ * Absent ⇒ the row is treated as flat and nothing nested is touched.
24
+ */
25
+ readonly mapValueKeys?: readonly string[];
18
26
  /** Why they are dead — cited in the record, not re-derived at the call site. */
19
27
  readonly reason: string;
20
28
  }
@@ -24,6 +32,8 @@ export interface RetiredKeyPurgeResult {
24
32
  readonly collection: string;
25
33
  readonly row: string;
26
34
  readonly keys: readonly string[];
35
+ /** `<mapKey>.<retiredKey>` for every nested deletion. Empty for a flat row. */
36
+ readonly nested: readonly string[];
27
37
  }
28
38
  /** The store access the purge needs. Narrow on purpose — trivially fakeable. */
29
39
  export interface RetiredKeyStore {
@@ -81,6 +91,11 @@ export declare const RETIRED_SETTINGS_KEYS: readonly RetiredKeySpec[];
81
91
  * routes it to the "read failed, row left untouched" branch.
82
92
  */
83
93
  export declare function retiredKeyStoreOf(backend: ISettingsBackend): RetiredKeyStore;
94
+ /**
95
+ * Nested `<mapKey>.<retiredKey>` paths that `spec` retires inside a map-shaped
96
+ * row. Pure. Values that are not plain objects are skipped, never rewritten.
97
+ */
98
+ export declare function planNestedRetiredKeyPurge(blob: Readonly<Record<string, unknown>>, spec: RetiredKeySpec): readonly string[];
84
99
  /** Keys of `blob` that `spec` retires. Pure. */
85
100
  export declare function planRetiredKeyPurge(blob: Readonly<Record<string, unknown>>, spec: RetiredKeySpec): readonly string[];
86
101
  /**
@@ -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-DgPUarcs.js");
6
+ const require_dist = require("../../dist-DP3nHlNM.js");
7
7
  let node_crypto = require("node:crypto");
8
8
  let node_fs = require("node:fs");
9
9
  let node_module = require("node:module");
@@ -131,6 +131,14 @@ var RETIRED_SETTINGS_KEYS = [{
131
131
  "probedBestEngine"
132
132
  ],
133
133
  reason: "global step/engine seed and the per-node engine cascade — both removed; the live per-camera dispatch path never read either"
134
+ }, {
135
+ namespace: "device-manager",
136
+ collection: "addon-settings",
137
+ row: "deviceMeta",
138
+ keys: [],
139
+ perNodeKeys: [],
140
+ mapValueKeys: ["deviceLinks"],
141
+ reason: "cross-device field wiring, deleted 2026-08-08 — 0 links across 302 devices a month after the UI shipped. There is no reader left: the resolver, the overlay, the cycle guard, the fifth binding kind and the tab are gone"
134
142
  }];
135
143
  /**
136
144
  * Adapt the settings backend to {@link RetiredKeyStore}.
@@ -158,6 +166,21 @@ function retiredKeyStoreOf(backend) {
158
166
  }
159
167
  };
160
168
  }
169
+ /**
170
+ * Nested `<mapKey>.<retiredKey>` paths that `spec` retires inside a map-shaped
171
+ * row. Pure. Values that are not plain objects are skipped, never rewritten.
172
+ */
173
+ function planNestedRetiredKeyPurge(blob, spec) {
174
+ const nestedKeys = spec.mapValueKeys ?? [];
175
+ if (nestedKeys.length === 0) return [];
176
+ const found = [];
177
+ for (const [mapKey, value] of Object.entries(blob)) {
178
+ const inner = require_dist.asJsonObject(value);
179
+ if (inner === null) continue;
180
+ for (const retired of nestedKeys) if (retired in inner) found.push(`${mapKey}.${retired}`);
181
+ }
182
+ return found;
183
+ }
161
184
  /** Keys of `blob` that `spec` retires. Pure. */
162
185
  function planRetiredKeyPurge(blob, spec) {
163
186
  const exact = new Set(spec.keys);
@@ -200,18 +223,35 @@ async function purgeRetiredSettingsKeys(store, logger, specs = RETIRED_SETTINGS_
200
223
  }
201
224
  if (blob === null) continue;
202
225
  const doomed = planRetiredKeyPurge(blob, spec);
203
- if (doomed.length === 0) continue;
226
+ const doomedNested = planNestedRetiredKeyPurge(blob, spec);
227
+ if (doomed.length === 0 && doomedNested.length === 0) continue;
204
228
  const doomedSet = new Set(doomed);
205
- const next = Object.fromEntries(Object.entries(blob).filter(([key]) => !doomedSet.has(key)));
229
+ const nestedByMapKey = /* @__PURE__ */ new Map();
230
+ for (const path of doomedNested) {
231
+ const cut = path.indexOf(".");
232
+ const mapKey = path.slice(0, cut);
233
+ const inner = path.slice(cut + 1);
234
+ const bucket = nestedByMapKey.get(mapKey) ?? /* @__PURE__ */ new Set();
235
+ bucket.add(inner);
236
+ nestedByMapKey.set(mapKey, bucket);
237
+ }
238
+ const next = Object.fromEntries(Object.entries(blob).filter(([key]) => !doomedSet.has(key)).map(([key, value]) => {
239
+ const retired = nestedByMapKey.get(key);
240
+ if (retired === void 0) return [key, value];
241
+ const inner = require_dist.asJsonObject(value);
242
+ if (inner === null) return [key, value];
243
+ return [key, Object.fromEntries(Object.entries(inner).filter(([k]) => !retired.has(k)))];
244
+ }));
206
245
  await store.write(spec, next);
207
246
  const result = {
208
247
  addon: spec.namespace,
209
248
  collection: spec.collection,
210
249
  row: spec.row,
211
- keys: doomed
250
+ keys: doomed,
251
+ nested: doomedNested
212
252
  };
213
253
  results.push(result);
214
- logger.info(`purged ${doomed.length} retired settings keys`, { meta: {
254
+ logger.info(`purged ${doomed.length + doomedNested.length} retired settings keys`, { meta: {
215
255
  ...result,
216
256
  reason: spec.reason
217
257
  } });
@@ -1134,6 +1174,25 @@ var PROMOTED_COLUMNS = [
1134
1174
  ];
1135
1175
  //#endregion
1136
1176
  //#region src/builtins/sqlite-storage/vector-index-vec.ts
1177
+ /**
1178
+ * The largest `k` sqlite-vec will accept in a KNN query.
1179
+ *
1180
+ * This is the EXTENSION's own compile-time limit, not a number chosen here —
1181
+ * measured against the real extension, which answers a larger `k` with
1182
+ * `SqliteError: k value in knn query too large, provided 20000 and the limit
1183
+ * is 4096` rather than with rows.
1184
+ *
1185
+ * It matters that this is the extension's number. The vector door looked like
1186
+ * a bypass of {@link SETTINGS_QUERY_HARD_ROW_CAP}: `vector-store.cap.ts`
1187
+ * declares `topK: z.number().int().positive()` with no `.max()`, and it is
1188
+ * bound straight into `k = ?` on the same connection, in the same process, as
1189
+ * the capped `data-store-provider.query`. It is not a bypass — sqlite-vec was
1190
+ * holding the line the whole time, which caps a KNN's heap cost at 4 096 rows
1191
+ * of metadata. What was actually broken is that it held the line by THROWING,
1192
+ * so an over-large `topK` reached the caller as a raw SQLite error instead of
1193
+ * as the bounded answer it could always have been served.
1194
+ */
1195
+ var VEC_KNN_MAX_K = 4096;
1137
1196
  var tableFor = (index) => `vec_${index.replace(/[^a-zA-Z0-9_]/g, "_")}`;
1138
1197
  var registryTable = "vec_indexes";
1139
1198
  /** `vec0` speaks packed Float32; the capability speaks base64 of the same. */
@@ -1274,10 +1333,17 @@ var SqliteVecVectorIndex = class {
1274
1333
  if (require_dist.decodeVectorBase64(params.vector).length !== spec.dim) throw new Error(`vector-store: query vector has ${require_dist.decodeVectorBase64(params.vector).length} dims, index "${params.index}" has ${spec.dim}`);
1275
1334
  const { clauses, values, unpushable } = compileFilter(params.filter);
1276
1335
  const where = clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "";
1336
+ const k = Math.min(Math.max(Math.floor(params.topK), 1), VEC_KNN_MAX_K);
1337
+ if (k !== params.topK) this.logger.warn("sqlite-vec: topK clamped to the extension ceiling", { meta: {
1338
+ index: params.index,
1339
+ requested: params.topK,
1340
+ k,
1341
+ ceiling: VEC_KNN_MAX_K
1342
+ } });
1277
1343
  const rows = this.db.prepare(`SELECT id, distance, deviceId, timestamp, className, modelId, extra
1278
1344
  FROM ${tableFor(params.index)}
1279
1345
  WHERE embedding MATCH ? AND k = ?${where}
1280
- ORDER BY distance`).all(toBlob(params.vector), BigInt(params.topK), ...values);
1346
+ ORDER BY distance`).all(toBlob(params.vector), BigInt(k), ...values);
1281
1347
  const minScore = params.minScore ?? Number.NEGATIVE_INFINITY;
1282
1348
  const matches = [];
1283
1349
  for (const raw of rows) {
@@ -1299,7 +1365,7 @@ var SqliteVecVectorIndex = class {
1299
1365
  return {
1300
1366
  matches,
1301
1367
  scanned: rows.length,
1302
- truncated: false
1368
+ truncated: k < params.topK && rows.length >= k
1303
1369
  };
1304
1370
  }
1305
1371
  async getByIds(index, ids) {
@@ -1,4 +1,4 @@
1
- import { Ct as asJsonObject, Pt as parseJsonUnknown, T as decodeVectorBase64, dt as vectorStoreCapability, ft as errMsg, l as RUNTIME_DEFAULTS, pt as BaseAddon, ut as vectorDimFromBase64, w as dataStoreProviderCapability, x as bareAddonId } from "../../dist-DnKpFaP1.mjs";
1
+ import { D as decodeVectorBase64, E as dataStoreProviderCapability, Mt as parseJsonUnknown, ct as vectorDimFromBase64, dt as BaseAddon, lt as vectorStoreCapability, u as RUNTIME_DEFAULTS, ut as errMsg, x as bareAddonId, xt as asJsonObject } from "../../dist-CMb7aKS0.mjs";
2
2
  import { createRequire } from "node:module";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { statSync } from "node:fs";
@@ -125,6 +125,14 @@ var RETIRED_SETTINGS_KEYS = [{
125
125
  "probedBestEngine"
126
126
  ],
127
127
  reason: "global step/engine seed and the per-node engine cascade — both removed; the live per-camera dispatch path never read either"
128
+ }, {
129
+ namespace: "device-manager",
130
+ collection: "addon-settings",
131
+ row: "deviceMeta",
132
+ keys: [],
133
+ perNodeKeys: [],
134
+ mapValueKeys: ["deviceLinks"],
135
+ reason: "cross-device field wiring, deleted 2026-08-08 — 0 links across 302 devices a month after the UI shipped. There is no reader left: the resolver, the overlay, the cycle guard, the fifth binding kind and the tab are gone"
128
136
  }];
129
137
  /**
130
138
  * Adapt the settings backend to {@link RetiredKeyStore}.
@@ -152,6 +160,21 @@ function retiredKeyStoreOf(backend) {
152
160
  }
153
161
  };
154
162
  }
163
+ /**
164
+ * Nested `<mapKey>.<retiredKey>` paths that `spec` retires inside a map-shaped
165
+ * row. Pure. Values that are not plain objects are skipped, never rewritten.
166
+ */
167
+ function planNestedRetiredKeyPurge(blob, spec) {
168
+ const nestedKeys = spec.mapValueKeys ?? [];
169
+ if (nestedKeys.length === 0) return [];
170
+ const found = [];
171
+ for (const [mapKey, value] of Object.entries(blob)) {
172
+ const inner = asJsonObject(value);
173
+ if (inner === null) continue;
174
+ for (const retired of nestedKeys) if (retired in inner) found.push(`${mapKey}.${retired}`);
175
+ }
176
+ return found;
177
+ }
155
178
  /** Keys of `blob` that `spec` retires. Pure. */
156
179
  function planRetiredKeyPurge(blob, spec) {
157
180
  const exact = new Set(spec.keys);
@@ -194,18 +217,35 @@ async function purgeRetiredSettingsKeys(store, logger, specs = RETIRED_SETTINGS_
194
217
  }
195
218
  if (blob === null) continue;
196
219
  const doomed = planRetiredKeyPurge(blob, spec);
197
- if (doomed.length === 0) continue;
220
+ const doomedNested = planNestedRetiredKeyPurge(blob, spec);
221
+ if (doomed.length === 0 && doomedNested.length === 0) continue;
198
222
  const doomedSet = new Set(doomed);
199
- const next = Object.fromEntries(Object.entries(blob).filter(([key]) => !doomedSet.has(key)));
223
+ const nestedByMapKey = /* @__PURE__ */ new Map();
224
+ for (const path of doomedNested) {
225
+ const cut = path.indexOf(".");
226
+ const mapKey = path.slice(0, cut);
227
+ const inner = path.slice(cut + 1);
228
+ const bucket = nestedByMapKey.get(mapKey) ?? /* @__PURE__ */ new Set();
229
+ bucket.add(inner);
230
+ nestedByMapKey.set(mapKey, bucket);
231
+ }
232
+ const next = Object.fromEntries(Object.entries(blob).filter(([key]) => !doomedSet.has(key)).map(([key, value]) => {
233
+ const retired = nestedByMapKey.get(key);
234
+ if (retired === void 0) return [key, value];
235
+ const inner = asJsonObject(value);
236
+ if (inner === null) return [key, value];
237
+ return [key, Object.fromEntries(Object.entries(inner).filter(([k]) => !retired.has(k)))];
238
+ }));
200
239
  await store.write(spec, next);
201
240
  const result = {
202
241
  addon: spec.namespace,
203
242
  collection: spec.collection,
204
243
  row: spec.row,
205
- keys: doomed
244
+ keys: doomed,
245
+ nested: doomedNested
206
246
  };
207
247
  results.push(result);
208
- logger.info(`purged ${doomed.length} retired settings keys`, { meta: {
248
+ logger.info(`purged ${doomed.length + doomedNested.length} retired settings keys`, { meta: {
209
249
  ...result,
210
250
  reason: spec.reason
211
251
  } });
@@ -1128,6 +1168,25 @@ var PROMOTED_COLUMNS = [
1128
1168
  ];
1129
1169
  //#endregion
1130
1170
  //#region src/builtins/sqlite-storage/vector-index-vec.ts
1171
+ /**
1172
+ * The largest `k` sqlite-vec will accept in a KNN query.
1173
+ *
1174
+ * This is the EXTENSION's own compile-time limit, not a number chosen here —
1175
+ * measured against the real extension, which answers a larger `k` with
1176
+ * `SqliteError: k value in knn query too large, provided 20000 and the limit
1177
+ * is 4096` rather than with rows.
1178
+ *
1179
+ * It matters that this is the extension's number. The vector door looked like
1180
+ * a bypass of {@link SETTINGS_QUERY_HARD_ROW_CAP}: `vector-store.cap.ts`
1181
+ * declares `topK: z.number().int().positive()` with no `.max()`, and it is
1182
+ * bound straight into `k = ?` on the same connection, in the same process, as
1183
+ * the capped `data-store-provider.query`. It is not a bypass — sqlite-vec was
1184
+ * holding the line the whole time, which caps a KNN's heap cost at 4 096 rows
1185
+ * of metadata. What was actually broken is that it held the line by THROWING,
1186
+ * so an over-large `topK` reached the caller as a raw SQLite error instead of
1187
+ * as the bounded answer it could always have been served.
1188
+ */
1189
+ var VEC_KNN_MAX_K = 4096;
1131
1190
  var tableFor = (index) => `vec_${index.replace(/[^a-zA-Z0-9_]/g, "_")}`;
1132
1191
  var registryTable = "vec_indexes";
1133
1192
  /** `vec0` speaks packed Float32; the capability speaks base64 of the same. */
@@ -1268,10 +1327,17 @@ var SqliteVecVectorIndex = class {
1268
1327
  if (decodeVectorBase64(params.vector).length !== spec.dim) throw new Error(`vector-store: query vector has ${decodeVectorBase64(params.vector).length} dims, index "${params.index}" has ${spec.dim}`);
1269
1328
  const { clauses, values, unpushable } = compileFilter(params.filter);
1270
1329
  const where = clauses.length > 0 ? ` AND ${clauses.join(" AND ")}` : "";
1330
+ const k = Math.min(Math.max(Math.floor(params.topK), 1), VEC_KNN_MAX_K);
1331
+ if (k !== params.topK) this.logger.warn("sqlite-vec: topK clamped to the extension ceiling", { meta: {
1332
+ index: params.index,
1333
+ requested: params.topK,
1334
+ k,
1335
+ ceiling: VEC_KNN_MAX_K
1336
+ } });
1271
1337
  const rows = this.db.prepare(`SELECT id, distance, deviceId, timestamp, className, modelId, extra
1272
1338
  FROM ${tableFor(params.index)}
1273
1339
  WHERE embedding MATCH ? AND k = ?${where}
1274
- ORDER BY distance`).all(toBlob(params.vector), BigInt(params.topK), ...values);
1340
+ ORDER BY distance`).all(toBlob(params.vector), BigInt(k), ...values);
1275
1341
  const minScore = params.minScore ?? Number.NEGATIVE_INFINITY;
1276
1342
  const matches = [];
1277
1343
  for (const raw of rows) {
@@ -1293,7 +1359,7 @@ var SqliteVecVectorIndex = class {
1293
1359
  return {
1294
1360
  matches,
1295
1361
  scanned: rows.length,
1296
- truncated: false
1362
+ truncated: k < params.topK && rows.length >= k
1297
1363
  };
1298
1364
  }
1299
1365
  async getByIds(index, ids) {
@@ -12,6 +12,25 @@ export interface RawSqliteDatabase {
12
12
  };
13
13
  loadExtension?(path: string): void;
14
14
  }
15
+ /**
16
+ * The largest `k` sqlite-vec will accept in a KNN query.
17
+ *
18
+ * This is the EXTENSION's own compile-time limit, not a number chosen here —
19
+ * measured against the real extension, which answers a larger `k` with
20
+ * `SqliteError: k value in knn query too large, provided 20000 and the limit
21
+ * is 4096` rather than with rows.
22
+ *
23
+ * It matters that this is the extension's number. The vector door looked like
24
+ * a bypass of {@link SETTINGS_QUERY_HARD_ROW_CAP}: `vector-store.cap.ts`
25
+ * declares `topK: z.number().int().positive()` with no `.max()`, and it is
26
+ * bound straight into `k = ?` on the same connection, in the same process, as
27
+ * the capped `data-store-provider.query`. It is not a bypass — sqlite-vec was
28
+ * holding the line the whole time, which caps a KNN's heap cost at 4 096 rows
29
+ * of metadata. What was actually broken is that it held the line by THROWING,
30
+ * so an over-large `topK` reached the caller as a raw SQLite error instead of
31
+ * as the bounded answer it could always have been served.
32
+ */
33
+ export declare const VEC_KNN_MAX_K = 4096;
15
34
  export interface SqliteVecIndexDeps {
16
35
  readonly db: RawSqliteDatabase;
17
36
  readonly logger: IScopedLogger;
@@ -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-DgPUarcs.js");
6
+ const require_dist = require("../../dist-DP3nHlNM.js");
7
7
  let node_path = require("node:path");
8
8
  node_path = require_chunk.__toESM(node_path);
9
9
  let node_fs_promises = require("node:fs/promises");
@@ -1,4 +1,4 @@
1
- import { Nt as parseJsonObject, f as StorageLocationTypeSchema, it as storageCapability, nt as settingsStoreCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
1
+ import { dt as BaseAddon, it as storageCapability, jt as parseJsonObject, nt as settingsStoreCapability, p as StorageLocationTypeSchema } from "../../dist-CMb7aKS0.mjs";
2
2
  import * as path$1 from "node:path";
3
3
  import * as fs from "node:fs/promises";
4
4
  import { buildStorageLocationRegistry } from "@camstack/system";
@@ -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-DgPUarcs.js");
6
+ const require_dist = require("../../dist-DP3nHlNM.js");
7
7
  //#region src/builtins/system-config/system-config.addon.ts
8
8
  /**
9
9
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -1,4 +1,4 @@
1
- import { At as hydrateSchema, ft as errMsg, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
1
+ import { Ot as hydrateSchema, dt as BaseAddon, ut as errMsg } from "../../dist-CMb7aKS0.mjs";
2
2
  //#region src/builtins/system-config/system-config.addon.ts
3
3
  /**
4
4
  * Built-in `system-config` addon — Phase 4 of the settings redesign.
@@ -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-DgPUarcs.js");
6
+ const require_dist = require("../../dist-DP3nHlNM.js");
7
7
  const require_formatter = require("../../formatter-DqAKDlvN.js");
8
8
  let node_path = require("node:path");
9
9
  node_path = require_chunk.__toESM(node_path);
@@ -1,4 +1,4 @@
1
- import { G as logDestinationCapability, pt as BaseAddon } from "../../dist-DnKpFaP1.mjs";
1
+ import { G as logDestinationCapability, dt as BaseAddon } from "../../dist-CMb7aKS0.mjs";
2
2
  import { t as formatLogLine } from "../../formatter-B7qW8bPJ.mjs";
3
3
  import * as path$1 from "node:path";
4
4
  import path from "node:path";