@trillboards/edge-core 0.2.1 → 0.2.3

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 (40) hide show
  1. package/dist/JsonFileStore.d.ts +33 -0
  2. package/dist/JsonFileStore.d.ts.map +1 -0
  3. package/dist/JsonFileStore.js +210 -0
  4. package/dist/JsonFileStore.js.map +1 -0
  5. package/dist/MemoryAttenuationManager.d.ts +164 -0
  6. package/dist/MemoryAttenuationManager.d.ts.map +1 -0
  7. package/dist/MemoryAttenuationManager.js +400 -0
  8. package/dist/MemoryAttenuationManager.js.map +1 -0
  9. package/dist/ModelManager.d.ts +166 -0
  10. package/dist/ModelManager.d.ts.map +1 -0
  11. package/dist/ModelManager.js +580 -0
  12. package/dist/ModelManager.js.map +1 -0
  13. package/dist/SignalBuffer.d.ts +20 -12
  14. package/dist/SignalBuffer.d.ts.map +1 -1
  15. package/dist/SignalBuffer.js +56 -71
  16. package/dist/SignalBuffer.js.map +1 -1
  17. package/dist/SignalStore.d.ts +35 -0
  18. package/dist/SignalStore.d.ts.map +1 -0
  19. package/dist/SignalStore.js +13 -0
  20. package/dist/SignalStore.js.map +1 -0
  21. package/dist/SqliteStore.d.ts +41 -0
  22. package/dist/SqliteStore.d.ts.map +1 -0
  23. package/dist/SqliteStore.js +162 -0
  24. package/dist/SqliteStore.js.map +1 -0
  25. package/dist/StatusServer.d.ts +99 -0
  26. package/dist/StatusServer.d.ts.map +1 -0
  27. package/dist/StatusServer.js +262 -0
  28. package/dist/StatusServer.js.map +1 -0
  29. package/dist/ZKAudienceProver.d.ts +137 -0
  30. package/dist/ZKAudienceProver.d.ts.map +1 -0
  31. package/dist/ZKAudienceProver.js +321 -0
  32. package/dist/ZKAudienceProver.js.map +1 -0
  33. package/dist/index.d.ts +12 -0
  34. package/dist/index.d.ts.map +1 -1
  35. package/dist/index.js +15 -1
  36. package/dist/index.js.map +1 -1
  37. package/dist/types.d.ts +40 -0
  38. package/dist/types.d.ts.map +1 -1
  39. package/dist/types.js.map +1 -1
  40. package/package.json +4 -2
@@ -0,0 +1,580 @@
1
+ "use strict";
2
+ /**
3
+ * ModelManager — Model lifecycle management with OTA (over-the-air) updates.
4
+ * Port of CTV agent's ModelManager.kt (387 lines), adapted for Node.js/ONNX.
5
+ *
6
+ * Manages registration, version-checking, and atomic download of ML model files.
7
+ * Downloads use a .tmp + rename pattern to prevent corrupted model loads.
8
+ * The version check timer is unref()'d so it does not keep the process alive.
9
+ *
10
+ * Events emitted:
11
+ * 'modelUpdated' — { name, oldVersion, newVersion, path }
12
+ * 'updateAvailable' — { name, currentVersion, availableVersion }
13
+ * 'downloadProgress' — { name, bytesDownloaded, totalBytes, percent }
14
+ * 'downloadError' — { name, error }
15
+ * 'checkComplete' — { checkedCount, updatesAvailable }
16
+ */
17
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ var desc = Object.getOwnPropertyDescriptor(m, k);
20
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
21
+ desc = { enumerable: true, get: function() { return m[k]; } };
22
+ }
23
+ Object.defineProperty(o, k2, desc);
24
+ }) : (function(o, m, k, k2) {
25
+ if (k2 === undefined) k2 = k;
26
+ o[k2] = m[k];
27
+ }));
28
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
29
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
30
+ }) : function(o, v) {
31
+ o["default"] = v;
32
+ });
33
+ var __importStar = (this && this.__importStar) || function (mod) {
34
+ if (mod && mod.__esModule) return mod;
35
+ var result = {};
36
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
37
+ __setModuleDefault(result, mod);
38
+ return result;
39
+ };
40
+ Object.defineProperty(exports, "__esModule", { value: true });
41
+ exports.ModelManager = void 0;
42
+ const events_1 = require("events");
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const https = __importStar(require("https"));
46
+ const http = __importStar(require("http"));
47
+ const crypto = __importStar(require("crypto"));
48
+ // ─── Implementation ─────────────────────────────────────────────────────────
49
+ class ModelManager extends events_1.EventEmitter {
50
+ constructor(options) {
51
+ super();
52
+ this.models = new Map();
53
+ this.checkTimer = null;
54
+ this.downloading = new Set();
55
+ this.options = {
56
+ modelsDir: options.modelsDir,
57
+ apiBaseUrl: options.apiBaseUrl.replace(/\/+$/, ''),
58
+ deviceFingerprint: options.deviceFingerprint,
59
+ checkIntervalMs: options.checkIntervalMs ?? 3600000,
60
+ maxConcurrentDownloads: options.maxConcurrentDownloads ?? 1,
61
+ autoUpdate: options.autoUpdate ?? true,
62
+ requestTimeoutMs: options.requestTimeoutMs ?? 30000,
63
+ downloadTimeoutMs: options.downloadTimeoutMs ?? 300000,
64
+ maxRedirects: options.maxRedirects ?? 5,
65
+ };
66
+ this.manifestPath = path.join(this.options.modelsDir, 'models_manifest.json');
67
+ }
68
+ // ─── Lifecycle ──────────────────────────────────────────────────────────────
69
+ /**
70
+ * Initialize — ensure models directory exists, load manifest, compute
71
+ * hashes for any registered model files already on disk.
72
+ */
73
+ async initialize() {
74
+ fs.mkdirSync(this.options.modelsDir, { recursive: true });
75
+ this.loadManifest();
76
+ // For every model that was loaded from the manifest, verify the file
77
+ // is still on disk and recompute its hash if needed.
78
+ const verifyPromises = [];
79
+ for (const [name, info] of this.models) {
80
+ verifyPromises.push(this.verifyModelOnDisk(name, info));
81
+ }
82
+ await Promise.all(verifyPromises);
83
+ }
84
+ /**
85
+ * Start periodic version checking. The timer is unref()'d so it does not
86
+ * prevent the Node.js process from exiting naturally.
87
+ */
88
+ start() {
89
+ if (this.checkTimer)
90
+ return;
91
+ this.checkTimer = setInterval(() => {
92
+ this.checkForUpdates().catch((err) => {
93
+ this.emit('downloadError', {
94
+ name: '*',
95
+ error: `Periodic check failed: ${String(err)}`,
96
+ });
97
+ });
98
+ }, this.options.checkIntervalMs);
99
+ // unref() so this timer alone does not keep the process alive
100
+ this.checkTimer.unref();
101
+ }
102
+ /**
103
+ * Stop periodic checking and cancel any in-flight state tracking.
104
+ */
105
+ stop() {
106
+ if (this.checkTimer) {
107
+ clearInterval(this.checkTimer);
108
+ this.checkTimer = null;
109
+ }
110
+ }
111
+ // ─── Registration ───────────────────────────────────────────────────────────
112
+ /**
113
+ * Register a model to track. Called during EdgeAgent init for each model type.
114
+ * If the file exists on disk, its SHA-256 is computed immediately.
115
+ * If the manifest already has version info for this name, it is merged.
116
+ */
117
+ registerModel(name, filePath) {
118
+ const absPath = path.resolve(filePath);
119
+ const existing = this.models.get(name);
120
+ const info = {
121
+ name,
122
+ version: existing?.version ?? '0.0.0',
123
+ filePath: absPath,
124
+ sha256: existing?.sha256 ?? '',
125
+ sizeBytes: existing?.sizeBytes ?? 0,
126
+ lastChecked: existing?.lastChecked ?? 0,
127
+ lastUpdated: existing?.lastUpdated ?? 0,
128
+ };
129
+ // If the file exists, populate size synchronously (hash is async, done in initialize)
130
+ if (fs.existsSync(absPath)) {
131
+ const stat = fs.statSync(absPath);
132
+ info.sizeBytes = stat.size;
133
+ }
134
+ this.models.set(name, info);
135
+ }
136
+ // ─── Update Checking ───────────────────────────────────────────────────────
137
+ /**
138
+ * Check for updates for all registered models by calling the backend API.
139
+ * Endpoint: GET /v1/federated/models?deviceFingerprint=X&modelType=Y
140
+ *
141
+ * Returns the list of ModelVersionResponse objects for which a newer
142
+ * version is available. If autoUpdate is enabled, downloads are
143
+ * initiated automatically (respecting maxConcurrentDownloads).
144
+ */
145
+ async checkForUpdates() {
146
+ const updates = [];
147
+ for (const [name, info] of this.models) {
148
+ try {
149
+ const encoded = encodeURIComponent(this.options.deviceFingerprint);
150
+ const apiPath = `/v1/federated/models?deviceFingerprint=${encoded}&modelType=${encodeURIComponent(name)}`;
151
+ const raw = await this.apiGet(apiPath);
152
+ if (!raw || typeof raw !== 'object')
153
+ continue;
154
+ // The API may return a single object or an array; normalize to single.
155
+ const response = Array.isArray(raw) ? raw[0] : raw;
156
+ if (!response || !response.version)
157
+ continue;
158
+ info.lastChecked = Date.now();
159
+ if (this.isNewerVersion(response.version, info.version)) {
160
+ updates.push(response);
161
+ this.emit('updateAvailable', {
162
+ name,
163
+ currentVersion: info.version,
164
+ availableVersion: response.version,
165
+ });
166
+ }
167
+ }
168
+ catch (err) {
169
+ // A failed check for one model must not abort the others
170
+ this.emit('downloadError', {
171
+ name,
172
+ error: `Version check failed: ${String(err)}`,
173
+ });
174
+ }
175
+ }
176
+ this.emit('checkComplete', {
177
+ checkedCount: this.models.size,
178
+ updatesAvailable: updates.length,
179
+ });
180
+ // Persist lastChecked timestamps
181
+ this.saveManifest();
182
+ // Auto-download if enabled
183
+ if (this.options.autoUpdate) {
184
+ for (const update of updates) {
185
+ if (this.downloading.size >= this.options.maxConcurrentDownloads)
186
+ break;
187
+ // Fire-and-forget — errors are emitted as events
188
+ this.downloadUpdate(update).catch(() => {
189
+ // Error already emitted inside downloadUpdate
190
+ });
191
+ }
192
+ }
193
+ return updates;
194
+ }
195
+ /**
196
+ * Download a specific model update.
197
+ *
198
+ * 1. Download to a .tmp file alongside the target path
199
+ * 2. Verify SHA-256 checksum matches the expected value
200
+ * 3. Atomic swap: rename .tmp -> target .onnx path
201
+ * 4. Update manifest with new version info
202
+ * 5. Emit 'modelUpdated' event
203
+ *
204
+ * Returns true if the model was successfully downloaded and verified.
205
+ */
206
+ async downloadUpdate(update) {
207
+ const modelName = update.name;
208
+ const info = this.models.get(modelName);
209
+ if (!info) {
210
+ this.emit('downloadError', {
211
+ name: modelName,
212
+ error: `Model '${modelName}' is not registered`,
213
+ });
214
+ return false;
215
+ }
216
+ if (this.downloading.has(modelName)) {
217
+ this.emit('downloadError', {
218
+ name: modelName,
219
+ error: `Download already in progress for '${modelName}'`,
220
+ });
221
+ return false;
222
+ }
223
+ this.downloading.add(modelName);
224
+ const tmpPath = info.filePath + '.tmp';
225
+ const oldVersion = info.version;
226
+ try {
227
+ // Ensure parent directory exists
228
+ const dir = path.dirname(info.filePath);
229
+ fs.mkdirSync(dir, { recursive: true });
230
+ // Step 1: Download to .tmp file
231
+ await this.downloadFile(update.downloadUrl, tmpPath, update.sizeBytes, modelName);
232
+ // Step 2: Verify SHA-256
233
+ const actualHash = await this.computeFileHash(tmpPath);
234
+ if (actualHash !== update.sha256) {
235
+ // Hash mismatch — clean up and report
236
+ this.cleanupTmpFile(tmpPath);
237
+ const msg = `SHA-256 mismatch for '${modelName}': expected ${update.sha256}, got ${actualHash}`;
238
+ this.emit('downloadError', { name: modelName, error: msg });
239
+ return false;
240
+ }
241
+ // Step 3: Atomic swap — rename .tmp to the target path
242
+ fs.renameSync(tmpPath, info.filePath);
243
+ // Step 4: Update model info and manifest
244
+ const stat = fs.statSync(info.filePath);
245
+ info.version = update.version;
246
+ info.sha256 = update.sha256;
247
+ info.sizeBytes = stat.size;
248
+ info.lastUpdated = Date.now();
249
+ info.lastChecked = Date.now();
250
+ this.saveManifest();
251
+ // Step 5: Emit success event
252
+ this.emit('modelUpdated', {
253
+ name: modelName,
254
+ oldVersion,
255
+ newVersion: update.version,
256
+ path: info.filePath,
257
+ });
258
+ return true;
259
+ }
260
+ catch (err) {
261
+ this.cleanupTmpFile(tmpPath);
262
+ this.emit('downloadError', {
263
+ name: modelName,
264
+ error: `Download failed for '${modelName}': ${String(err)}`,
265
+ });
266
+ return false;
267
+ }
268
+ finally {
269
+ this.downloading.delete(modelName);
270
+ }
271
+ }
272
+ // ─── Query Methods ──────────────────────────────────────────────────────────
273
+ /**
274
+ * Get info about a registered model. Returns null if not registered.
275
+ */
276
+ getModel(name) {
277
+ return this.models.get(name) ?? null;
278
+ }
279
+ /**
280
+ * Get all registered models.
281
+ */
282
+ getModels() {
283
+ return Array.from(this.models.values());
284
+ }
285
+ /**
286
+ * Check if a model's file exists on disk.
287
+ */
288
+ hasModel(name) {
289
+ const info = this.models.get(name);
290
+ if (!info)
291
+ return false;
292
+ return fs.existsSync(info.filePath);
293
+ }
294
+ /**
295
+ * Check if a download is currently in progress for a model.
296
+ */
297
+ isDownloading(name) {
298
+ return this.downloading.has(name);
299
+ }
300
+ // ─── Private: File Hashing ──────────────────────────────────────────────────
301
+ /**
302
+ * Compute the SHA-256 hex digest of a file using a streaming reader.
303
+ */
304
+ computeFileHash(filePath) {
305
+ return new Promise((resolve, reject) => {
306
+ const hash = crypto.createHash('sha256');
307
+ const stream = fs.createReadStream(filePath);
308
+ stream.on('data', (chunk) => hash.update(chunk));
309
+ stream.on('end', () => resolve(hash.digest('hex')));
310
+ stream.on('error', reject);
311
+ });
312
+ }
313
+ // ─── Private: File Download ─────────────────────────────────────────────────
314
+ /**
315
+ * Download a file from a URL to a local destination path.
316
+ * Follows HTTP redirects (up to maxRedirects), emits progress events,
317
+ * and enforces a download timeout.
318
+ */
319
+ downloadFile(url, dest, expectedSize, modelName) {
320
+ return this.downloadFileWithRedirects(url, dest, expectedSize, modelName, 0);
321
+ }
322
+ downloadFileWithRedirects(url, dest, expectedSize, modelName, redirectCount) {
323
+ return new Promise((resolve, reject) => {
324
+ if (redirectCount > this.options.maxRedirects) {
325
+ reject(new Error(`Too many redirects (>${this.options.maxRedirects}) downloading ${modelName}`));
326
+ return;
327
+ }
328
+ const isHttps = url.startsWith('https://');
329
+ const transport = isHttps ? https : http;
330
+ const parsedUrl = new URL(url);
331
+ const requestOptions = {
332
+ hostname: parsedUrl.hostname,
333
+ port: parsedUrl.port || (isHttps ? 443 : 80),
334
+ path: parsedUrl.pathname + parsedUrl.search,
335
+ method: 'GET',
336
+ timeout: this.options.downloadTimeoutMs,
337
+ headers: {
338
+ 'User-Agent': `TrillboardsEdgeSDK/1.0 (${this.options.deviceFingerprint})`,
339
+ },
340
+ };
341
+ const req = transport.request(requestOptions, (res) => {
342
+ // Handle redirects
343
+ if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
344
+ // Drain the response to free the socket
345
+ res.resume();
346
+ const redirectUrl = res.headers.location.startsWith('http')
347
+ ? res.headers.location
348
+ : new URL(res.headers.location, url).toString();
349
+ this.downloadFileWithRedirects(redirectUrl, dest, expectedSize, modelName, redirectCount + 1)
350
+ .then(resolve)
351
+ .catch(reject);
352
+ return;
353
+ }
354
+ if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
355
+ // Drain and reject
356
+ res.resume();
357
+ reject(new Error(`HTTP ${res.statusCode} downloading '${modelName}' from ${url}`));
358
+ return;
359
+ }
360
+ const totalBytes = expectedSize
361
+ ?? (res.headers['content-length'] ? parseInt(res.headers['content-length'], 10) : 0);
362
+ let bytesDownloaded = 0;
363
+ const fileStream = fs.createWriteStream(dest);
364
+ res.on('data', (chunk) => {
365
+ bytesDownloaded += chunk.length;
366
+ const percent = totalBytes > 0
367
+ ? Math.min(100, Math.round((bytesDownloaded / totalBytes) * 100))
368
+ : 0;
369
+ this.emit('downloadProgress', {
370
+ name: modelName,
371
+ bytesDownloaded,
372
+ totalBytes,
373
+ percent,
374
+ });
375
+ });
376
+ res.pipe(fileStream);
377
+ fileStream.on('finish', () => {
378
+ fileStream.close(() => resolve());
379
+ });
380
+ fileStream.on('error', (err) => {
381
+ fileStream.close(() => {
382
+ this.cleanupTmpFile(dest);
383
+ reject(err);
384
+ });
385
+ });
386
+ res.on('error', (err) => {
387
+ fileStream.close(() => {
388
+ this.cleanupTmpFile(dest);
389
+ reject(err);
390
+ });
391
+ });
392
+ });
393
+ req.on('timeout', () => {
394
+ req.destroy();
395
+ this.cleanupTmpFile(dest);
396
+ reject(new Error(`Download timeout (${this.options.downloadTimeoutMs}ms) for '${modelName}'`));
397
+ });
398
+ req.on('error', (err) => {
399
+ this.cleanupTmpFile(dest);
400
+ reject(err);
401
+ });
402
+ req.end();
403
+ });
404
+ }
405
+ // ─── Private: Manifest Persistence ──────────────────────────────────────────
406
+ /**
407
+ * Load model manifest from disk. If the file does not exist or is corrupt,
408
+ * start with an empty manifest — never crash.
409
+ */
410
+ loadManifest() {
411
+ try {
412
+ if (!fs.existsSync(this.manifestPath))
413
+ return;
414
+ const raw = fs.readFileSync(this.manifestPath, 'utf-8');
415
+ const data = JSON.parse(raw);
416
+ if (!data || typeof data !== 'object' || !data.models)
417
+ return;
418
+ for (const [name, entry] of Object.entries(data.models)) {
419
+ if (!entry || typeof entry !== 'object')
420
+ continue;
421
+ // If the model is already registered (via registerModel before initialize),
422
+ // merge manifest info into it. Otherwise create a placeholder entry.
423
+ const existing = this.models.get(name);
424
+ if (existing) {
425
+ existing.version = entry.version || existing.version;
426
+ existing.sha256 = entry.sha256 || existing.sha256;
427
+ existing.lastUpdated = entry.lastUpdated || existing.lastUpdated;
428
+ }
429
+ else {
430
+ // Model not yet registered — store manifest data; filePath will be
431
+ // set when registerModel is called later.
432
+ this.models.set(name, {
433
+ name,
434
+ version: entry.version || '0.0.0',
435
+ filePath: '',
436
+ sha256: entry.sha256 || '',
437
+ sizeBytes: 0,
438
+ lastChecked: data.lastChecked || 0,
439
+ lastUpdated: entry.lastUpdated || 0,
440
+ });
441
+ }
442
+ }
443
+ }
444
+ catch {
445
+ // Corrupt manifest — start fresh, never crash
446
+ console.warn(`[ModelManager] Corrupt manifest at ${this.manifestPath}, starting fresh`);
447
+ }
448
+ }
449
+ /**
450
+ * Save the current model state to the manifest file on disk.
451
+ * Uses atomic write: write to .tmp, then rename.
452
+ */
453
+ saveManifest() {
454
+ const models = {};
455
+ let lastChecked = 0;
456
+ for (const [name, info] of this.models) {
457
+ models[name] = {
458
+ version: info.version,
459
+ sha256: info.sha256,
460
+ lastUpdated: info.lastUpdated,
461
+ };
462
+ if (info.lastChecked > lastChecked) {
463
+ lastChecked = info.lastChecked;
464
+ }
465
+ }
466
+ const data = { models, lastChecked };
467
+ const tmpPath = this.manifestPath + '.tmp';
468
+ try {
469
+ fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
470
+ fs.renameSync(tmpPath, this.manifestPath);
471
+ }
472
+ catch (err) {
473
+ console.warn(`[ModelManager] Failed to save manifest: ${String(err)}`);
474
+ this.cleanupTmpFile(tmpPath);
475
+ }
476
+ }
477
+ // ─── Private: API Requests ──────────────────────────────────────────────────
478
+ /**
479
+ * Make an HTTPS GET request to the backend API and return parsed JSON.
480
+ */
481
+ apiGet(apiPath) {
482
+ return new Promise((resolve, reject) => {
483
+ const fullUrl = this.options.apiBaseUrl + apiPath;
484
+ const isHttps = fullUrl.startsWith('https://');
485
+ const transport = isHttps ? https : http;
486
+ const parsedUrl = new URL(fullUrl);
487
+ const requestOptions = {
488
+ hostname: parsedUrl.hostname,
489
+ port: parsedUrl.port || (isHttps ? 443 : 80),
490
+ path: parsedUrl.pathname + parsedUrl.search,
491
+ method: 'GET',
492
+ timeout: this.options.requestTimeoutMs,
493
+ headers: {
494
+ 'Accept': 'application/json',
495
+ 'User-Agent': `TrillboardsEdgeSDK/1.0 (${this.options.deviceFingerprint})`,
496
+ },
497
+ };
498
+ const req = transport.request(requestOptions, (res) => {
499
+ if (!res.statusCode || res.statusCode < 200 || res.statusCode >= 300) {
500
+ res.resume();
501
+ reject(new Error(`API request failed: HTTP ${res.statusCode} for ${apiPath}`));
502
+ return;
503
+ }
504
+ const chunks = [];
505
+ res.on('data', (chunk) => chunks.push(chunk));
506
+ res.on('end', () => {
507
+ try {
508
+ const body = Buffer.concat(chunks).toString('utf-8');
509
+ resolve(JSON.parse(body));
510
+ }
511
+ catch (err) {
512
+ reject(new Error(`Failed to parse API response for ${apiPath}: ${String(err)}`));
513
+ }
514
+ });
515
+ res.on('error', reject);
516
+ });
517
+ req.on('timeout', () => {
518
+ req.destroy();
519
+ reject(new Error(`API request timeout (${this.options.requestTimeoutMs}ms) for ${apiPath}`));
520
+ });
521
+ req.on('error', reject);
522
+ req.end();
523
+ });
524
+ }
525
+ // ─── Private: Helpers ───────────────────────────────────────────────────────
526
+ /**
527
+ * Verify that a model file exists on disk, recompute its hash if present,
528
+ * or clear stale info if the file has been deleted.
529
+ */
530
+ async verifyModelOnDisk(name, info) {
531
+ if (!info.filePath || !fs.existsSync(info.filePath)) {
532
+ // File missing — clear hash/size but keep version metadata
533
+ info.sha256 = '';
534
+ info.sizeBytes = 0;
535
+ return;
536
+ }
537
+ try {
538
+ const stat = fs.statSync(info.filePath);
539
+ info.sizeBytes = stat.size;
540
+ info.sha256 = await this.computeFileHash(info.filePath);
541
+ }
542
+ catch {
543
+ // If we cannot read the file, clear cached info
544
+ info.sha256 = '';
545
+ info.sizeBytes = 0;
546
+ }
547
+ }
548
+ /**
549
+ * Semver comparison: returns true if `available` is strictly newer than `current`.
550
+ * Handles non-semver gracefully by falling back to string comparison.
551
+ */
552
+ isNewerVersion(available, current) {
553
+ const parse = (v) => {
554
+ const parts = v.replace(/^v/, '').split('.').map(Number);
555
+ return [parts[0] || 0, parts[1] || 0, parts[2] || 0];
556
+ };
557
+ const [aMajor, aMinor, aPatch] = parse(available);
558
+ const [cMajor, cMinor, cPatch] = parse(current);
559
+ if (aMajor !== cMajor)
560
+ return aMajor > cMajor;
561
+ if (aMinor !== cMinor)
562
+ return aMinor > cMinor;
563
+ return aPatch > cPatch;
564
+ }
565
+ /**
566
+ * Safely remove a .tmp file if it exists. Never throws.
567
+ */
568
+ cleanupTmpFile(tmpPath) {
569
+ try {
570
+ if (fs.existsSync(tmpPath)) {
571
+ fs.unlinkSync(tmpPath);
572
+ }
573
+ }
574
+ catch {
575
+ // Best-effort cleanup; do not propagate
576
+ }
577
+ }
578
+ }
579
+ exports.ModelManager = ModelManager;
580
+ //# sourceMappingURL=ModelManager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ModelManager.js","sourceRoot":"","sources":["../src/ModelManager.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,22 @@
1
1
  /**
2
- * SignalBuffer — Offline signal buffering using better-sqlite3.
3
- * Port of SignalBuffer.kt (Room database) to cross-platform SQLite.
2
+ * SignalBuffer — Offline signal buffering with auto-detecting backend.
3
+ * Port of SignalBuffer.kt (Room database) to cross-platform storage.
4
+ *
5
+ * Backends:
6
+ * - SqliteStore (better-sqlite3 installed → WAL mode, prepared stmts)
7
+ * - JsonFileStore (fallback → zero deps, fs.renameSync atomic writes)
8
+ *
9
+ * Auto-detection: tries require('better-sqlite3') at construction time.
10
+ * If unavailable (native compilation failed, not installed), falls back
11
+ * to JsonFileStore — guaranteed to work on any Node.js >=18 platform.
4
12
  *
5
13
  * Capacity: 8,640 signals (24h at 6 signals/min)
6
14
  * Eviction: oldest 10% when at capacity
7
15
  * Flush: batch of 50, marks with _buffered and _bufferedAt
8
16
  * Pruning: signals older than 24h auto-deleted
17
+ * Priority: FACE_BACKED > PERSON_BACKED > SPEECH_ONLY > PROXY_ONLY > EMPTY
9
18
  */
19
+ import type { SignalStoreStats } from './SignalStore';
10
20
  import { SocketManager } from './SocketManager';
11
21
  export interface BufferedSignal {
12
22
  id: number;
@@ -20,15 +30,8 @@ export declare class SignalBuffer {
20
30
  static readonly MAX_AGE_MS: number;
21
31
  static readonly MAX_RETRIES = 5;
22
32
  static readonly EVICT_RATIO = 0.1;
23
- private db;
24
- private insertStmt;
25
- private getOldestStmt;
26
- private deleteByIdStmt;
27
- private countStmt;
28
- private pruneStmt;
29
- private deleteOldestNStmt;
30
- private incrementRetryStmt;
31
- private deleteMaxRetriesStmt;
33
+ private store;
34
+ readonly backend: 'sqlite' | 'json';
32
35
  constructor(dbPath: string);
33
36
  /**
34
37
  * Buffer a signal for later sending.
@@ -38,6 +41,7 @@ export declare class SignalBuffer {
38
41
  /**
39
42
  * Flush buffered signals via socket.
40
43
  * Returns number of successfully sent signals.
44
+ * Signals are flushed in priority order — highest evidence grade first.
41
45
  */
42
46
  flushBuffer(socketManager: SocketManager, limit?: number): number;
43
47
  /**
@@ -49,7 +53,11 @@ export declare class SignalBuffer {
49
53
  */
50
54
  count(): number;
51
55
  /**
52
- * Close the database connection.
56
+ * Signal buffer statistics — count by evidence grade, oldest timestamp.
57
+ */
58
+ stats(): SignalStoreStats;
59
+ /**
60
+ * Close the store connection.
53
61
  */
54
62
  shutdown(): void;
55
63
  }