@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.
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +91 -72
- package/dist/index.js.map +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/stats.d.ts +29 -0
- package/dist/routes/stats.d.ts.map +1 -1
- package/dist/routes/sync.d.ts.map +1 -1
- package/package.json +4 -4
- package/static/app.js +112 -22500
- package/static/app.js.br +0 -0
- package/static/app.js.gz +0 -0
- package/static/index.html +0 -1
- package/static/themes.css.br +0 -0
- package/static/themes.css.gz +0 -0
- package/static/tokens.css.br +0 -0
- package/static/tokens.css.gz +0 -0
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,
|
|
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 =
|
|
452
|
-
const configData = withoutRemovedConfigKeys(
|
|
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
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
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
|
-
|
|
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:
|
|
1533
|
-
tokens_written:
|
|
1534
|
-
tokens_saved:
|
|
1535
|
-
count:
|
|
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
|
|
1548
|
-
* scope
|
|
1549
|
-
*
|
|
1550
|
-
*
|
|
1551
|
-
* loop. Keyed by db path + scope identity +
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
1659
|
-
const
|
|
1660
|
-
const
|
|
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:
|
|
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
|
-
|
|
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.`);
|