@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.
- package/dist/addon-runner.js +1 -1
- package/dist/addon-runner.mjs +1 -1
- package/dist/addon-utils.js +1 -1
- package/dist/addon-utils.mjs +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
- package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
- package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
- package/dist/builtins/alerts/alerts.addon.js +1 -1
- package/dist/builtins/alerts/alerts.addon.mjs +1 -1
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +3 -3
- package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
- package/dist/builtins/console-logging/index.js +1 -1
- package/dist/builtins/console-logging/index.mjs +1 -1
- package/dist/builtins/core-blocks/block-package.d.ts +9 -27
- package/dist/builtins/core-blocks/block-supervisor.d.ts +15 -0
- package/dist/builtins/core-blocks/core-blocks.addon.js +91 -24
- package/dist/builtins/core-blocks/core-blocks.addon.mjs +91 -25
- package/dist/builtins/device-manager/device-bindings-store.d.ts +28 -5
- package/dist/builtins/device-manager/device-linked-devices.d.ts +2 -2
- package/dist/builtins/device-manager/device-manager.addon.d.ts +3 -45
- package/dist/builtins/device-manager/device-manager.addon.js +123 -884
- package/dist/builtins/device-manager/device-manager.addon.mjs +123 -884
- package/dist/builtins/device-manager/device-meta-actions.d.ts +0 -8
- package/dist/builtins/device-manager/device-meta-store.d.ts +1 -28
- package/dist/builtins/device-manager/device-meta-types.d.ts +2 -6
- package/dist/builtins/device-manager/device-projection.d.ts +1 -2
- package/dist/builtins/device-manager/device-provider-context.d.ts +2 -13
- package/dist/builtins/device-manager/device-queries.d.ts +1 -1
- package/dist/builtins/device-manager/device-state-mirror.d.ts +8 -49
- package/dist/builtins/device-manager/device-sync-resolvers.d.ts +0 -11
- package/dist/builtins/doorbell/virtual-doorbell.addon.js +1 -1
- package/dist/builtins/doorbell/virtual-doorbell.addon.mjs +1 -1
- package/dist/builtins/hub-forwarder/index.js +1 -1
- package/dist/builtins/hub-forwarder/index.mjs +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
- package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
- package/dist/builtins/local-auth/homeassistant-integration.d.ts +7 -0
- package/dist/builtins/local-auth/index.d.ts +1 -0
- package/dist/builtins/local-auth/index.js +1 -0
- package/dist/builtins/local-auth/index.mjs +2 -2
- package/dist/builtins/local-auth/local-auth.addon.js +561 -497
- package/dist/builtins/local-auth/local-auth.addon.mjs +580 -517
- package/dist/builtins/local-auth/oauth-grants.d.ts +11 -0
- package/dist/builtins/local-auth/oauth-session-manager.d.ts +1 -1
- package/dist/builtins/local-network/local-network.addon.js +1 -1
- package/dist/builtins/local-network/local-network.addon.mjs +1 -1
- package/dist/builtins/loki-logging/index.js +1 -1
- package/dist/builtins/loki-logging/index.mjs +1 -1
- package/dist/builtins/native-metrics/native-metrics.addon.js +2 -2
- package/dist/builtins/native-metrics/native-metrics.addon.mjs +2 -2
- package/dist/builtins/platform-probe/index.js +2 -2
- package/dist/builtins/platform-probe/index.mjs +2 -2
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
- package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
- package/dist/builtins/snapshot/index.js +319 -20
- package/dist/builtins/snapshot/index.mjs +319 -20
- package/dist/builtins/snapshot/snapshot-keep-warm.d.ts +86 -0
- package/dist/builtins/snapshot/snapshot-media-handler.d.ts +48 -1
- package/dist/builtins/snapshot/snapshot.addon.d.ts +65 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
- package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
- package/dist/builtins/sqlite-storage/retired-settings-keys.d.ts +15 -0
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +73 -7
- package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +73 -7
- package/dist/builtins/sqlite-storage/vector-index-vec.d.ts +19 -0
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
- package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
- package/dist/builtins/system-config/system-config.addon.js +1 -1
- package/dist/builtins/system-config/system-config.addon.mjs +1 -1
- package/dist/builtins/winston-logging/index.js +1 -1
- package/dist/builtins/winston-logging/index.mjs +1 -1
- package/dist/{dist-DnKpFaP1.mjs → dist-CMb7aKS0.mjs} +1227 -1330
- package/dist/{dist-DgPUarcs.js → dist-DP3nHlNM.js} +1251 -1366
- package/dist/http/asset-compression.d.ts +93 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +64 -9
- package/dist/index.mjs +65 -11
- package/dist/kernel/heap-watch.d.ts +55 -0
- package/dist/kernel/moleculer/device-cap-proxy.d.ts +2 -2
- package/dist/{manifest-python-deps-d4acYXVV.js → manifest-python-deps-BE6l8Y4W.js} +21 -15
- package/dist/{manifest-python-deps-CX5tGRqf.mjs → manifest-python-deps-Dz1ryHIN.mjs} +21 -15
- package/dist/{model-download-service-D8B-4ktF.mjs → model-download-service-CTBHzORJ.mjs} +220 -2
- package/dist/{model-download-service-Cpc6SWzV.js → model-download-service-Caj1kaSi.js} +220 -2
- package/package.json +5 -1
- package/dist/builtins/device-manager/device-link-cycle.d.ts +0 -18
- package/dist/builtins/device-manager/device-link-overlay.d.ts +0 -33
- package/dist/builtins/device-manager/device-link-resolver.d.ts +0 -44
- package/dist/main-CVLq8IzA.js +0 -2356
- 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
|
-
|
|
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-
|
|
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 {
|
|
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-
|
|
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
|
-
|
|
226
|
+
const doomedNested = planNestedRetiredKeyPurge(blob, spec);
|
|
227
|
+
if (doomed.length === 0 && doomedNested.length === 0) continue;
|
|
204
228
|
const doomedSet = new Set(doomed);
|
|
205
|
-
const
|
|
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(
|
|
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:
|
|
1368
|
+
truncated: k < params.topK && rows.length >= k
|
|
1303
1369
|
};
|
|
1304
1370
|
}
|
|
1305
1371
|
async getByIds(index, ids) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
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
|
-
|
|
220
|
+
const doomedNested = planNestedRetiredKeyPurge(blob, spec);
|
|
221
|
+
if (doomed.length === 0 && doomedNested.length === 0) continue;
|
|
198
222
|
const doomedSet = new Set(doomed);
|
|
199
|
-
const
|
|
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(
|
|
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:
|
|
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-
|
|
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 {
|
|
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-
|
|
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 {
|
|
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-
|
|
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,
|
|
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";
|