@codemem/server 0.36.1 → 0.37.1

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.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAiB,OAAO,EAAE,MAAM,eAAe,CAAC;AAE1F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAAE,OAAO,EAAE,CAAC;AAKnB,uFAAuF;AACvF,wBAAgB,QAAQ,IAAI,WAAW,CAKtC;AAED,mDAAmD;AACnD,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,YAAY,CAAC,EAAE,MAAM,WAAW,CAAC;IACjC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,oBAAoB,CAAC,EAAE;QACtB,OAAO,CAAC,EAAE,0BAA0B,CAAC;QACrC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,4BAA4B,CAAC,EAAE,MAAM,CAAC;KACtC,CAAC;IACF,oBAAoB,CAAC,EAAE,MAAM;QAC5B,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;QACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,GAAG,IAAI,CAAC;CACT;AAED,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,UAAU,8EA6D1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8B9C"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,KAAK,eAAe,EAAiB,OAAO,EAAE,MAAM,eAAe,CAAC;AAE1F,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAE5B,OAAO,EAEN,KAAK,0BAA0B,EAC/B,MAAM,yBAAyB,CAAC;AAQjC,OAAO,EAAE,OAAO,EAAE,CAAC;AAKnB,uFAAuF;AACvF,wBAAgB,QAAQ,IAAI,WAAW,CAKtC;AAED,mDAAmD;AACnD,wBAAgB,UAAU,IAAI,IAAI,CAGjC;AAED;;;GAGG;AACH,MAAM,WAAW,UAAU;IAC1B,YAAY,CAAC,EAAE,MAAM,WAAW,CAAC;IACjC,OAAO,CAAC,EAAE,eAAe,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,EAAE,cAAc,GAAG,IAAI,CAAC;IACjC,oBAAoB,CAAC,EAAE;QACtB,OAAO,CAAC,EAAE,0BAA0B,CAAC;QACrC,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,aAAa,CAAC,EAAE,MAAM,CAAC;QACvB,wBAAwB,CAAC,EAAE,MAAM,CAAC;QAClC,4BAA4B,CAAC,EAAE,MAAM,CAAC;KACtC,CAAC;IACF,oBAAoB,CAAC,EAAE,MAAM;QAC5B,KAAK,EACF,UAAU,GACV,SAAS,GACT,UAAU,GACV,OAAO,GACP,UAAU,GACV,iBAAiB,GACjB,iBAAiB,GACjB,IAAI,CAAC;QACR,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KACvB,GAAG,IAAI,CAAC;CACT;AAED,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8D1C;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,UAAU,8EA8B9C"}
package/dist/index.js CHANGED
@@ -1,14 +1,14 @@
1
1
  import { existsSync, readFileSync } from "node:fs";
2
2
  import { dirname, join } from "node:path";
3
- import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_CAPABILITY_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, diagnoseStalePeerReceivedRows, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeAddress, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripJsonComments, stripPrivateObj, stripTrailingCommas, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifySignature, writeCodememConfigFile } from "@codemem/core";
3
+ import { ACCESS_CLEANUP_OP_TYPE, CODEMEM_CONFIG_ENV_OVERRIDES, DEFAULT_TIME_WINDOW_S, LEGACY_SHARED_REVIEW_SCOPE_ID, LOCAL_DEFAULT_SCOPE_ID, LOCAL_SYNC_CAPABILITY, MemoryStore, SYNC_CAPABILITY_HEADER, SYNC_SCOPE_QUERY_PARAM, VERSION, VERSION as VERSION$1, addSyncScopeToBoundary, analyzeProjectScopeMappingChangeGuardrails, applyReplicationOps, buildBaseUrl, buildFilterClausesWithContext, buildRawEventEnvelopeFromCodexHook, buildRawEventEnvelopeFromHook, canonicalMemoryKind, canonicalWorkspaceIdentity, cleanupNonces, coordinatorArchiveGroupAction, coordinatorCreateGroupAction, coordinatorCreateInviteAction, coordinatorCreateScopeAction, coordinatorDisableDeviceAction, coordinatorEnableDeviceAction, coordinatorGrantScopeMembershipAction, coordinatorImportInviteAction, coordinatorListBootstrapGrantsAction, coordinatorListDevicesAction, coordinatorListGroupsAction, coordinatorListJoinRequestsAction, coordinatorListScopeMembershipsAction, coordinatorListScopesAction, coordinatorRemoveDeviceAction, coordinatorRenameDeviceAction, coordinatorRenameGroupAction, coordinatorReviewJoinRequestAction, coordinatorRevokeBootstrapGrantAction, coordinatorRevokeScopeMembershipAction, coordinatorStatusSnapshot, coordinatorUnarchiveGroupAction, coordinatorUpdateScopeAction, createCoordinatorReciprocalApproval, defaultSpaceScopeIdForGroup, deleteProjectScopeSettingsMapping, diagnoseStalePeerReceivedRows, ensureDeviceIdentity, extractReplicationOps, filterReplicationOpsForSyncWithStatus, fingerprintPublicKey, formatHostPort, fromJson, getCodememConfigPath, getCodememEnvOverrides, getCoordinatorGroupPreference, getSemanticIndexDiagnostics, getSyncResetState, isScopedSyncCapability, isSummaryLikeMemory, listAuthorizedScopesForPeer, listCoordinatorJoinRequests, listInboundScopeRejections, listMaintenanceJobs, listObserverProviderOptions, listPerPeerScopeSyncState, listProjectScopeCandidates, listProjectScopeInventory, listProjectScopeSettingsMappings, listSharingDomainSettingsScopes, loadMemorySnapshotPageForPeer, loadReplicationOpsForPeer, lookupCoordinatorPeers, mergeAddresses, negotiateSyncCapability, normalizeAddress, normalizeSyncCapability, parseStrictInteger, parseSyncScopeRequest, personalScopeGrantStatusForPeer, probeAvailableCredentials, readCodememConfigFile, readCoordinatorSyncConfig, reassignProjectScopeInventoryProject, recordNonce, rejectInboundScopeFailures, requestJson, resolveDbPath, runSyncPass, schema, stripPrivateObj, summarizeInboundScopeRejections, syncScopeResetRequiredPayload, updatePeerAddresses, upsertCoordinatorGroupPreference, upsertProjectScopeSettingsMapping, verifySignature, writeCodememConfigFile } from "@codemem/core";
4
4
  import { serveStatic } from "@hono/node-server/serve-static";
5
5
  import { Hono } from "hono";
6
6
  import { createMiddleware } from "hono/factory";
7
7
  import { RateLimiter } from "limiter";
8
- import { homedir, networkInterfaces } from "node:os";
9
8
  import { and, count, desc, eq, inArray, max, ne } from "drizzle-orm";
10
9
  import { drizzle } from "drizzle-orm/better-sqlite3";
11
10
  import { createHash, randomUUID } from "node:crypto";
11
+ import { networkInterfaces } from "node:os";
12
12
  //#region src/middleware.ts
13
13
  var LOOPBACK_HOSTS = new Set([
14
14
  "127.0.0.1",
@@ -252,27 +252,6 @@ var DEFAULTS = {
252
252
  function loadProviderOptions() {
253
253
  return listObserverProviderOptions();
254
254
  }
255
- function getConfigPath() {
256
- const envPath = process.env.CODEMEM_CONFIG;
257
- if (envPath) return envPath.replace(/^~/, homedir());
258
- const configDir = join(homedir(), ".config", "codemem");
259
- return [join(configDir, "config.json"), join(configDir, "config.jsonc")].find((p) => existsSync(p)) ?? join(configDir, "config.json");
260
- }
261
- function readConfigFile(configPath) {
262
- if (!existsSync(configPath)) return {};
263
- try {
264
- let text = readFileSync(configPath, "utf-8").trim();
265
- if (!text) return {};
266
- try {
267
- return JSON.parse(text);
268
- } catch {
269
- text = stripTrailingCommas(stripJsonComments(text));
270
- return JSON.parse(text);
271
- }
272
- } catch {
273
- return {};
274
- }
275
- }
276
255
  function withoutRemovedConfigKeys(configData) {
277
256
  const next = { ...configData };
278
257
  for (const key of REMOVED_CONFIG_KEYS) delete next[key];
@@ -448,8 +427,8 @@ function applyRuntimeEffects(changedKeys, opts) {
448
427
  function configRoutes(opts = {}) {
449
428
  const app = new Hono();
450
429
  app.get("/api/config", (c) => {
451
- const configPath = getConfigPath();
452
- const configData = withoutRemovedConfigKeys(readConfigFile(configPath));
430
+ const configPath = getCodememConfigPath();
431
+ const configData = withoutRemovedConfigKeys(readCodememConfigFile());
453
432
  const effective = getEffectiveConfig(configData);
454
433
  return c.json({
455
434
  path: configPath,
@@ -1509,30 +1488,29 @@ function sanitizePackUsageMetadata(metadata, visibility) {
1509
1488
  for (const key of MEMORY_ID_METADATA_KEYS) if (Array.isArray(sanitized[key])) sanitized[key] = metadataMemoryIds(sanitized[key]).filter((memoryId) => visibility.visibleMemoryIds.has(memoryId));
1510
1489
  return sanitized;
1511
1490
  }
1512
- function summarizeUsageEvents(rows) {
1513
- const byEvent = /* @__PURE__ */ new Map();
1514
- for (const row of rows) {
1515
- const summary = byEvent.get(row.event) ?? {
1516
- event: row.event,
1517
- total_tokens_read: 0,
1518
- total_tokens_written: 0,
1519
- total_tokens_saved: 0,
1520
- count: 0
1521
- };
1522
- summary.total_tokens_read += row.tokens_read;
1523
- summary.total_tokens_written += row.tokens_written;
1524
- summary.total_tokens_saved += row.tokens_saved;
1525
- summary.count += 1;
1526
- byEvent.set(row.event, summary);
1527
- }
1528
- return [...byEvent.values()].sort((a, b) => a.event.localeCompare(b.event));
1491
+ /**
1492
+ * Convert the neutral core aggregate rows into the wire shape for /api/usage
1493
+ * (ApiUsageEventSummary: total_tokens_* names), sorted by event name ASC to
1494
+ * match the route's historical ordering.
1495
+ */
1496
+ function mapAggregateEvents(rows) {
1497
+ return rows.map((row) => ({
1498
+ event: row.event,
1499
+ total_tokens_read: row.tokens_read,
1500
+ total_tokens_written: row.tokens_written,
1501
+ total_tokens_saved: row.tokens_saved,
1502
+ count: row.count
1503
+ })).sort((a, b) => a.event.localeCompare(b.event));
1529
1504
  }
1530
- function totalUsageEvents(rows) {
1505
+ /**
1506
+ * Sum the neutral core aggregate rows into the ApiUsageTotals wire shape.
1507
+ */
1508
+ function totalsFromAggregate(rows) {
1531
1509
  return rows.reduce((acc, row) => ({
1532
- tokens_read: Number(acc.tokens_read) + row.tokens_read,
1533
- tokens_written: Number(acc.tokens_written) + row.tokens_written,
1534
- tokens_saved: Number(acc.tokens_saved) + row.tokens_saved,
1535
- count: Number(acc.count) + 1
1510
+ tokens_read: acc.tokens_read + row.tokens_read,
1511
+ tokens_written: acc.tokens_written + row.tokens_written,
1512
+ tokens_saved: acc.tokens_saved + row.tokens_saved,
1513
+ count: acc.count + row.count
1536
1514
  }), {
1537
1515
  tokens_read: 0,
1538
1516
  tokens_written: 0,
@@ -1544,16 +1522,48 @@ function totalUsageEvents(rows) {
1544
1522
  * Short-lived cache for the computed /api/usage payload.
1545
1523
  *
1546
1524
  * The health dot polls /api/usage on every 5s refresh regardless of the
1547
- * active tab, and the computation scans the full usage_events table to apply
1548
- * scope visibility before aggregating. The resulting token totals are
1549
- * cumulative dashboard figures, so a few seconds of staleness is acceptable
1550
- * in exchange for collapsing the repeated full-table scan on the polling
1551
- * loop. Keyed by db path + scope identity + project filter so distinct
1552
- * stores/projects never share an entry.
1525
+ * active tab. The token/event totals come from indexed SQL aggregates and the
1526
+ * recent-pack window runs scope-visibility resolution over its bounded set, so
1527
+ * each miss is cheap — but the totals are cumulative dashboard figures where a
1528
+ * few seconds of staleness is acceptable, so caching still collapses the
1529
+ * repeated recompute on the polling loop. Keyed by db path + scope identity +
1530
+ * project filter so distinct stores/projects never share an entry.
1553
1531
  */
1554
1532
  var usagePayloadCache = /* @__PURE__ */ new Map();
1555
1533
  var USAGE_PAYLOAD_CACHE_MS = 1e4;
1556
1534
  /**
1535
+ * Hard cap on cached usage payloads. The cache key folds in the arbitrary
1536
+ * `?project=` filter and a scope-visibility fingerprint that becomes
1537
+ * per-request-unique on the error/bypass path, so without a bound the map
1538
+ * would grow without limit in a long-running viewer. The cap is far above the
1539
+ * realistic working set (a handful of projects times one or two scope
1540
+ * generations), so legitimate hits are never evicted under normal load.
1541
+ */
1542
+ var USAGE_PAYLOAD_CACHE_MAX_ENTRIES = 256;
1543
+ /**
1544
+ * Sweep expired entries and enforce the size cap before inserting a new one.
1545
+ *
1546
+ * Mirrors the lazy-sweep approach in request-rate-limit.ts: eviction is
1547
+ * piggybacked on writes rather than run on a timer, so an idle viewer does no
1548
+ * background work. Expired entries (those past `expiresAtMs`) are always
1549
+ * dropped; if the map is still at capacity afterward — e.g. a burst of
1550
+ * distinct, still-live bypass keys — the oldest-expiring entries are evicted
1551
+ * to make room. This never changes hit/miss semantics for a live entry: the
1552
+ * caller still re-checks `expiresAtMs` on read, and only already-expired or
1553
+ * over-cap entries are removed here.
1554
+ */
1555
+ function sweepUsagePayloadCache(cache, nowMs) {
1556
+ for (const [key, entry] of cache) if (entry.expiresAtMs <= nowMs) cache.delete(key);
1557
+ if (cache.size < USAGE_PAYLOAD_CACHE_MAX_ENTRIES) return;
1558
+ const byExpiry = [...cache.entries()].sort((a, b) => a[1].expiresAtMs - b[1].expiresAtMs);
1559
+ let overflow = cache.size - USAGE_PAYLOAD_CACHE_MAX_ENTRIES + 1;
1560
+ for (const [key] of byExpiry) {
1561
+ if (overflow <= 0) break;
1562
+ cache.delete(key);
1563
+ overflow -= 1;
1564
+ }
1565
+ }
1566
+ /**
1557
1567
  * Cheap fingerprint of the state that drives scope visibility
1558
1568
  * (`scope_memberships` / `replication_scopes`). Folding this into the usage
1559
1569
  * cache key ensures a membership/scope status change — e.g. a revocation —
@@ -1643,30 +1653,31 @@ function statsRoutes(getStore) {
1643
1653
  const nowMs = Date.now();
1644
1654
  const cached = usagePayloadCache.get(cacheKey);
1645
1655
  if (cached && nowMs < cached.expiresAtMs) return c.json(cached.payload);
1646
- const loadUsageRows = (project) => {
1656
+ const globalAggregate = store.usageAggregate();
1657
+ const eventsGlobal = mapAggregateEvents(globalAggregate);
1658
+ const totalsGlobal = totalsFromAggregate(globalAggregate);
1659
+ const filteredAggregate = projectFilter ? store.usageAggregate(projectFilter) : null;
1660
+ const eventsFiltered = filteredAggregate ? mapAggregateEvents(filteredAggregate) : null;
1661
+ const totalsFiltered = filteredAggregate ? totalsFromAggregate(filteredAggregate) : null;
1662
+ const RECENT_PACK_SCAN_LIMIT = 200;
1663
+ const loadRecentPackWindow = (project) => {
1647
1664
  return (project ? store.db.prepare(`SELECT usage_events.id, usage_events.session_id, usage_events.event,
1648
1665
  usage_events.tokens_read, usage_events.tokens_written, usage_events.tokens_saved,
1649
1666
  usage_events.created_at, usage_events.metadata_json
1650
1667
  FROM usage_events
1651
1668
  JOIN sessions ON sessions.id = usage_events.session_id
1652
- WHERE sessions.project = ?
1653
- ORDER BY usage_events.created_at DESC`).all(project) : store.db.prepare(`SELECT id, session_id, event, tokens_read, tokens_written, tokens_saved,
1669
+ WHERE sessions.project = ? AND usage_events.event = 'pack'
1670
+ ORDER BY usage_events.created_at DESC
1671
+ LIMIT ?`).all(project, RECENT_PACK_SCAN_LIMIT) : store.db.prepare(`SELECT id, session_id, event, tokens_read, tokens_written, tokens_saved,
1654
1672
  created_at, metadata_json
1655
1673
  FROM usage_events
1656
- ORDER BY created_at DESC`).all()).map((row) => toUsageRow(row, parseMetadataJson(row.metadata_json)));
1674
+ WHERE event = 'pack'
1675
+ ORDER BY created_at DESC
1676
+ LIMIT ?`).all(RECENT_PACK_SCAN_LIMIT)).map((row) => toUsageRow(row, parseMetadataJson(row.metadata_json)));
1657
1677
  };
1658
- const loadedGlobalRows = loadUsageRows();
1659
- const globalVisibility = buildUsageVisibility(store, loadedGlobalRows);
1660
- const globalRows = loadedGlobalRows.filter((row) => usageRowVisible(row, globalVisibility));
1661
- const loadedFilteredRows = projectFilter ? loadUsageRows(projectFilter) : null;
1662
- const filteredVisibility = loadedFilteredRows ? buildUsageVisibility(store, loadedFilteredRows) : null;
1663
- const filteredRows = loadedFilteredRows ? loadedFilteredRows.filter((row) => usageRowVisible(row, filteredVisibility ?? globalVisibility)) : null;
1664
- const eventsGlobal = summarizeUsageEvents(globalRows);
1665
- const totalsGlobal = totalUsageEvents(globalRows);
1666
- const eventsFiltered = filteredRows ? summarizeUsageEvents(filteredRows) : null;
1667
- const totalsFiltered = filteredRows ? totalUsageEvents(filteredRows) : null;
1668
- const recentVisibility = filteredVisibility ?? globalVisibility;
1669
- const recentPacks = (filteredRows ?? globalRows).filter((row) => row.event === "pack").slice(0, 10).map((row) => ({
1678
+ const recentWindow = loadRecentPackWindow(projectFilter);
1679
+ const recentVisibility = buildUsageVisibility(store, recentWindow);
1680
+ const recentPacks = recentWindow.filter((row) => usageRowVisible(row, recentVisibility)).slice(0, 10).map((row) => ({
1670
1681
  ...row,
1671
1682
  metadata_json: sanitizePackUsageMetadata(row.metadata_json, recentVisibility)
1672
1683
  }));
@@ -1680,9 +1691,11 @@ function statsRoutes(getStore) {
1680
1691
  totals_filtered: totalsFiltered,
1681
1692
  recent_packs: recentPacks
1682
1693
  };
1694
+ const setAtMs = Date.now();
1695
+ sweepUsagePayloadCache(usagePayloadCache, setAtMs);
1683
1696
  usagePayloadCache.set(cacheKey, {
1684
1697
  payload,
1685
- expiresAtMs: Date.now() + USAGE_PAYLOAD_CACHE_MS
1698
+ expiresAtMs: setAtMs + USAGE_PAYLOAD_CACHE_MS
1686
1699
  });
1687
1700
  return c.json(payload);
1688
1701
  }
@@ -3650,7 +3663,12 @@ function syncRoutes(getStore, getSyncRuntimeStatus) {
3650
3663
  const store = getStore();
3651
3664
  {
3652
3665
  const d = drizzle(store.db, { schema });
3653
- const body = await c.req.json();
3666
+ let body;
3667
+ try {
3668
+ body = await c.req.json();
3669
+ } catch {
3670
+ return c.json({ error: "invalid json" }, 400);
3671
+ }
3654
3672
  const peerDeviceId = String(body.peer_device_id ?? "").trim();
3655
3673
  const name = String(body.name ?? "").trim();
3656
3674
  if (!peerDeviceId) return c.json({ error: "peer_device_id required" }, 400);
@@ -5460,7 +5478,8 @@ function createApp(opts) {
5460
5478
  });
5461
5479
  app.use("/assets/*", serveStatic({
5462
5480
  root: staticRoot,
5463
- rewriteRequestPath: (path) => path.replace(/^\/assets/, "")
5481
+ rewriteRequestPath: (path) => path.replace(/^\/assets/, ""),
5482
+ precompressed: true
5464
5483
  }));
5465
5484
  const indexPath = join(staticRoot, "index.html");
5466
5485
  if (!existsSync(indexPath)) throw new Error(`Viewer assets missing at ${indexPath}. Run \`pnpm build\` from the repo root before starting the viewer.`);