tokentrace 0.16.0 → 0.17.0
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/CHANGELOG.md +58 -0
- package/app/models/page.tsx +1 -1
- package/app/page.tsx +72 -47
- package/app/projects/page.tsx +1 -1
- package/app/tools/page.tsx +1 -1
- package/components/charts/rank-bar-chart-lazy.tsx +9 -0
- package/components/charts/skeleton.tsx +16 -0
- package/components/charts/trend-section-lazy.tsx +9 -0
- package/components/overview/current-mix-panel.tsx +1 -1
- package/components/overview/section-skeletons.tsx +35 -0
- package/dist/runtime/agent.mjs +29 -6
- package/dist/runtime/db-migrate.mjs +5 -0
- package/dist/runtime/db-seed.mjs +5 -0
- package/dist/runtime/digest.mjs +25 -2
- package/dist/runtime/doctor.mjs +81 -19
- package/dist/runtime/evidence.mjs +5 -0
- package/dist/runtime/insights.mjs +25 -2
- package/dist/runtime/pricing-refresh.mjs +27 -4
- package/dist/runtime/repair.mjs +27 -4
- package/dist/runtime/report.mjs +30 -6
- package/dist/runtime/reset.mjs +5 -0
- package/dist/runtime/review.mjs +25 -2
- package/dist/runtime/scan.mjs +152 -102
- package/dist/runtime/status.mjs +5 -0
- package/next.config.mjs +28 -5
- package/package.json +1 -1
- package/scripts/doctor.ts +38 -11
- package/server.json +2 -2
- package/src/db/client.ts +5 -0
- package/src/db/prepared.ts +17 -0
- package/src/ingestion/discovery.ts +2 -5
- package/src/ingestion/persist.ts +12 -3
- package/src/ingestion/scan-adapters.ts +11 -0
- package/src/ingestion/scan-files.ts +35 -24
- package/src/ingestion/scan.ts +6 -8
- package/src/lib/analytics-query-helpers.ts +2 -2
- package/src/lib/doctor-cli.ts +45 -0
- package/src/lib/overview-data.ts +140 -46
- package/src/lib/scheduled-scan.ts +4 -4
- package/src/lib/source-catalog.ts +5 -3
- package/src/lib/unknown-cost-repair/suggestions.ts +2 -2
- package/src/lib/unknown-cost-repair/workbench.ts +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,64 @@ All notable changes to TokenTrace are documented here.
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## [0.17.0] - 2026-05-23
|
|
8
|
+
|
|
9
|
+
### Performance
|
|
10
|
+
|
|
11
|
+
- **Runtime SQLite pragmas tuned for analytics.** `src/db/client.ts` now
|
|
12
|
+
re-applies `journal_mode=WAL` on the live connection and sets
|
|
13
|
+
`synchronous=NORMAL`, `temp_store=MEMORY`, `cache_size=64MB`, and
|
|
14
|
+
`mmap_size=256MB`. Previously only `busy_timeout` and `foreign_keys`
|
|
15
|
+
were set on the runtime connection.
|
|
16
|
+
- **Prepared-statement cache.** `src/db/prepared.ts` adds a tiny
|
|
17
|
+
`prepareCached(sql)` helper keyed by SQL string. The hot analytics,
|
|
18
|
+
unknown-cost-repair, scheduled-scan, and ingestion helpers now skip
|
|
19
|
+
the parse-and-plan cost on repeat queries.
|
|
20
|
+
- **Overview page parallelization.** The independent sub-queries in
|
|
21
|
+
`getOverviewData` (analytics, accounting invariants, scan diff,
|
|
22
|
+
default search roots, repair workbench) now run through `Promise.all`,
|
|
23
|
+
so the async filesystem walk overlaps with the serialized SQLite
|
|
24
|
+
reads.
|
|
25
|
+
- **Render-scoped overview memo.** `getOverviewPageData` is wrapped in
|
|
26
|
+
`React.cache` so any future composition that calls it twice within a
|
|
27
|
+
single server render tree pays the cost once.
|
|
28
|
+
- **Lazy-loaded Recharts.** `TrendSection` and `RankBarChart` are now
|
|
29
|
+
loaded via `next/dynamic({ ssr: false })` on the overview, projects,
|
|
30
|
+
tools, and models routes, splitting the ~80KB Recharts bundle out of
|
|
31
|
+
the initial JS payload. `ChartSkeleton` keeps the chart slot from
|
|
32
|
+
collapsing during client hydration.
|
|
33
|
+
- **Streaming overview with two `<Suspense>` boundaries.** The overview
|
|
34
|
+
page now splits into two cache-wrapped fetchers — `getOverviewPrimaryData`
|
|
35
|
+
(analytics-driven: pulse, summary, trend, trust, guardrails,
|
|
36
|
+
recommendations, mix) and `getOverviewRepairData` (workbench-driven:
|
|
37
|
+
review status, repair items). Each renders inside its own `<Suspense>`
|
|
38
|
+
so the page shell and period filter paint immediately, the primary
|
|
39
|
+
analytics block streams in next, and the repair lane streams in
|
|
40
|
+
independently when the workbench query resolves.
|
|
41
|
+
- **`tokentrace doctor --timings`.** New flag force-enables analytics
|
|
42
|
+
timing capture and emits the analytics timing report (slow queries,
|
|
43
|
+
threshold, sample list). Combine with `--json` for machine-readable
|
|
44
|
+
output. Useful for before/after measurement of performance changes
|
|
45
|
+
since `TOKENTRACE_ANALYTICS_TIMING` is off by default in production.
|
|
46
|
+
- **Scan ingestion throughput.** Adds a (path, size, mtime)-keyed
|
|
47
|
+
file-hash cache so rescans of unchanged files skip the `fs.readFile`
|
|
48
|
+
+ SHA-256 step entirely. The hot scan-side `INSERT INTO scan_files`,
|
|
49
|
+
`INSERT INTO scan_runs`, and `hasImportedFile` lookups also route
|
|
50
|
+
through `prepareCached`.
|
|
51
|
+
- **Next bundle optimizations.** Enables `optimizePackageImports` for
|
|
52
|
+
`lucide-react` (~37 import sites) and `recharts` so Next 16 transforms
|
|
53
|
+
named imports into per-symbol imports and prunes unused-symbol weight
|
|
54
|
+
from the client bundle. Adds an opt-in `@next/bundle-analyzer`
|
|
55
|
+
integration gated on `ANALYZE=true` for follow-up audits.
|
|
56
|
+
- **Hot-path bug-hunt fixes.** `source-catalog.summarizeSourceCoverage`
|
|
57
|
+
was O(rows × entries); now uses a pre-built Map for O(1) parser tier
|
|
58
|
+
lookups. `ingestion/discovery.getDefaultSearchRoots` parallelizes its
|
|
59
|
+
`fs.access` checks via `Promise.all` instead of awaiting them
|
|
60
|
+
sequentially. `ingestion/persist.findProjectRoot` memoizes the
|
|
61
|
+
resolved project root by start directory, eliminating duplicate
|
|
62
|
+
filesystem walks when many imported sessions share the same source
|
|
63
|
+
directory.
|
|
64
|
+
|
|
7
65
|
## [0.16.0] - 2026-05-23
|
|
8
66
|
|
|
9
67
|
### Added
|
package/app/models/page.tsx
CHANGED
|
@@ -3,7 +3,7 @@ import { EmptyState } from "@/components/empty-state";
|
|
|
3
3
|
import { ScanNowButton } from "@/components/scan-now-button";
|
|
4
4
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
5
5
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
6
|
-
import { RankBarChart } from "@/components/charts/rank-bar-chart";
|
|
6
|
+
import { RankBarChart } from "@/components/charts/rank-bar-chart-lazy";
|
|
7
7
|
import { PageHeader } from "@/components/ui/typography";
|
|
8
8
|
import { getAnalyticsData } from "@/src/lib/analytics";
|
|
9
9
|
import { formatCurrency, formatTokens } from "@/src/lib/format";
|
package/app/page.tsx
CHANGED
|
@@ -1,12 +1,17 @@
|
|
|
1
|
+
import { Suspense } from "react";
|
|
1
2
|
import Link from "next/link";
|
|
2
3
|
import { ArrowRight } from "lucide-react";
|
|
3
|
-
import { TrendSection } from "@/components/charts/trend-section";
|
|
4
|
+
import { TrendSection } from "@/components/charts/trend-section-lazy";
|
|
4
5
|
import { OverviewCurrentMixPanel } from "@/components/overview/current-mix-panel";
|
|
5
6
|
import { DataConfidenceStrip } from "@/components/overview/data-confidence-strip";
|
|
6
7
|
import { FirstRunPanel } from "@/components/overview/first-run-panel";
|
|
7
8
|
import { UsageGuardrailsPanel } from "@/components/overview/guardrails-panel";
|
|
8
9
|
import { OverviewRecommendationsCard } from "@/components/overview/recommendations-card";
|
|
9
10
|
import { OverviewReviewStatusStrip } from "@/components/overview/review-status-strip";
|
|
11
|
+
import {
|
|
12
|
+
OverviewPrimarySkeleton,
|
|
13
|
+
OverviewRepairSkeleton
|
|
14
|
+
} from "@/components/overview/section-skeletons";
|
|
10
15
|
import { CostSessionsCard, TokenAccountingCard } from "@/components/overview/summary-cards";
|
|
11
16
|
import { TopRepairItemsStrip } from "@/components/overview/top-repair-items-strip";
|
|
12
17
|
import { OverviewTrustFooter } from "@/components/overview/trust-footer";
|
|
@@ -14,9 +19,9 @@ import { UsagePulsePanel } from "@/components/overview/usage-pulse-panel";
|
|
|
14
19
|
import { PeriodFilter } from "@/components/period-filter";
|
|
15
20
|
import { Button } from "@/components/ui/button";
|
|
16
21
|
import { PageHeader } from "@/components/ui/typography";
|
|
17
|
-
import { resolveDateRange } from "@/src/lib/date-range";
|
|
22
|
+
import { mergeHrefParams, type ResolvedDateRange, resolveDateRange } from "@/src/lib/date-range";
|
|
18
23
|
import { runDueScheduledScan } from "@/src/lib/scheduled-scan";
|
|
19
|
-
import {
|
|
24
|
+
import { getOverviewPrimaryData, getOverviewRepairData } from "@/src/lib/overview-data";
|
|
20
25
|
|
|
21
26
|
export const dynamic = "force-dynamic";
|
|
22
27
|
|
|
@@ -24,51 +29,24 @@ type OverviewPageProps = {
|
|
|
24
29
|
searchParams?: Promise<Record<string, string | string[] | undefined>>;
|
|
25
30
|
};
|
|
26
31
|
|
|
27
|
-
|
|
28
|
-
void runDueScheduledScan().catch(() => undefined);
|
|
29
|
-
const params = (await searchParams) ?? {};
|
|
30
|
-
const range = resolveDateRange(params);
|
|
31
|
-
const overview = await getOverviewPageData(range);
|
|
32
|
+
async function OverviewPrimarySection({ range }: { range: ResolvedDateRange }) {
|
|
32
33
|
const {
|
|
33
34
|
data,
|
|
34
35
|
trust,
|
|
35
|
-
accountingReport,
|
|
36
|
-
postSessionReview,
|
|
37
|
-
rangeLinkParams,
|
|
38
36
|
evidenceLinks,
|
|
39
|
-
doctorReport,
|
|
40
|
-
repairWorkbench,
|
|
41
|
-
repairFocusHref,
|
|
42
|
-
unknownCostEvidenceHref,
|
|
43
37
|
firstRunStatus,
|
|
44
38
|
summary,
|
|
45
|
-
trendDefaultWindow
|
|
46
|
-
|
|
39
|
+
trendDefaultWindow,
|
|
40
|
+
unknownCostEvidenceHref,
|
|
41
|
+
rangeLinkParams
|
|
42
|
+
} = await getOverviewPrimaryData(range);
|
|
43
|
+
const repairFocusHref = mergeHrefParams("/repair", rangeLinkParams);
|
|
47
44
|
|
|
48
45
|
return (
|
|
49
46
|
<div className="space-y-8">
|
|
50
|
-
<
|
|
51
|
-
title="Overview"
|
|
52
|
-
description="Local token, cost, model, and session analytics across AI CLI tools."
|
|
53
|
-
actions={
|
|
54
|
-
<Button asChild>
|
|
55
|
-
<Link href="/settings#scan-controls">
|
|
56
|
-
Configure scan <ArrowRight className="h-4 w-4" />
|
|
57
|
-
</Link>
|
|
58
|
-
</Button>
|
|
59
|
-
}
|
|
60
|
-
/>
|
|
61
|
-
|
|
62
|
-
<PeriodFilter range={range} />
|
|
63
|
-
|
|
64
|
-
{summary.interactions === 0 ? (
|
|
65
|
-
<FirstRunPanel status={firstRunStatus} />
|
|
66
|
-
) : null}
|
|
67
|
-
|
|
47
|
+
{summary.interactions === 0 ? <FirstRunPanel status={firstRunStatus} /> : null}
|
|
68
48
|
<UsagePulsePanel comparison={data.comparison} />
|
|
69
|
-
|
|
70
49
|
<DataConfidenceStrip confidence={data.dataConfidence} />
|
|
71
|
-
|
|
72
50
|
<div className="grid gap-3 md:grid-cols-2 xl:grid-cols-6">
|
|
73
51
|
<TokenAccountingCard
|
|
74
52
|
summary={summary}
|
|
@@ -84,11 +62,38 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
84
62
|
sessionsHref={evidenceLinks.sessions}
|
|
85
63
|
/>
|
|
86
64
|
</div>
|
|
87
|
-
|
|
88
65
|
<OverviewTrustFooter health={trust.health} pricedModelCount={trust.pricedModelCount} />
|
|
89
|
-
|
|
90
66
|
<TrendSection data={data.trends} defaultWindow={trendDefaultWindow} />
|
|
67
|
+
<UsageGuardrailsPanel progress={data.usageGuardrails} />
|
|
68
|
+
<OverviewRecommendationsCard recommendations={data.recommendations} />
|
|
69
|
+
<OverviewCurrentMixPanel
|
|
70
|
+
tools={data.tools}
|
|
71
|
+
mostUsedTool={summary.mostUsedTool}
|
|
72
|
+
mostUsedModel={summary.mostUsedModel}
|
|
73
|
+
/>
|
|
74
|
+
</div>
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function OverviewRepairSection({ range }: { range: ResolvedDateRange }) {
|
|
79
|
+
const {
|
|
80
|
+
accountingReport,
|
|
81
|
+
postSessionReview,
|
|
82
|
+
doctorReport,
|
|
83
|
+
repairWorkbench,
|
|
84
|
+
repairFocusHref,
|
|
85
|
+
evidenceLinks,
|
|
86
|
+
summary,
|
|
87
|
+
trust,
|
|
88
|
+
rangeLinkParams
|
|
89
|
+
} = await getOverviewRepairData(range);
|
|
91
90
|
|
|
91
|
+
if (summary.interactions === 0 && repairWorkbench.groups.length === 0) {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return (
|
|
96
|
+
<div className="space-y-8">
|
|
92
97
|
{summary.interactions > 0 ? (
|
|
93
98
|
<OverviewReviewStatusStrip
|
|
94
99
|
report={doctorReport}
|
|
@@ -102,7 +107,6 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
102
107
|
cachedEvidenceHref={evidenceLinks["cached-tokens"]}
|
|
103
108
|
/>
|
|
104
109
|
) : null}
|
|
105
|
-
|
|
106
110
|
{repairWorkbench.groups.length ? (
|
|
107
111
|
<TopRepairItemsStrip
|
|
108
112
|
groups={repairWorkbench.groups}
|
|
@@ -110,17 +114,38 @@ export default async function OverviewPage({ searchParams }: OverviewPageProps)
|
|
|
110
114
|
rangeLinkParams={rangeLinkParams}
|
|
111
115
|
/>
|
|
112
116
|
) : null}
|
|
117
|
+
</div>
|
|
118
|
+
);
|
|
119
|
+
}
|
|
113
120
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
export default async function OverviewPage({ searchParams }: OverviewPageProps) {
|
|
122
|
+
void runDueScheduledScan().catch(() => undefined);
|
|
123
|
+
const params = (await searchParams) ?? {};
|
|
124
|
+
const range = resolveDateRange(params);
|
|
117
125
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
126
|
+
return (
|
|
127
|
+
<div className="space-y-8">
|
|
128
|
+
<PageHeader
|
|
129
|
+
title="Overview"
|
|
130
|
+
description="Local token, cost, model, and session analytics across AI CLI tools."
|
|
131
|
+
actions={
|
|
132
|
+
<Button asChild>
|
|
133
|
+
<Link href="/settings#scan-controls">
|
|
134
|
+
Configure scan <ArrowRight className="h-4 w-4" />
|
|
135
|
+
</Link>
|
|
136
|
+
</Button>
|
|
137
|
+
}
|
|
122
138
|
/>
|
|
123
139
|
|
|
140
|
+
<PeriodFilter range={range} />
|
|
141
|
+
|
|
142
|
+
<Suspense fallback={<OverviewPrimarySkeleton />}>
|
|
143
|
+
<OverviewPrimarySection range={range} />
|
|
144
|
+
</Suspense>
|
|
145
|
+
|
|
146
|
+
<Suspense fallback={<OverviewRepairSkeleton />}>
|
|
147
|
+
<OverviewRepairSection range={range} />
|
|
148
|
+
</Suspense>
|
|
124
149
|
</div>
|
|
125
150
|
);
|
|
126
151
|
}
|
package/app/projects/page.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
|
-
import { RankBarChart } from "@/components/charts/rank-bar-chart";
|
|
2
|
+
import { RankBarChart } from "@/components/charts/rank-bar-chart-lazy";
|
|
3
3
|
import { TrendChart } from "@/components/charts/trend-chart";
|
|
4
4
|
import { EmptyState } from "@/components/empty-state";
|
|
5
5
|
import { ScanNowButton } from "@/components/scan-now-button";
|
package/app/tools/page.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RankBarChart } from "@/components/charts/rank-bar-chart";
|
|
1
|
+
import { RankBarChart } from "@/components/charts/rank-bar-chart-lazy";
|
|
2
2
|
import { EmptyState } from "@/components/empty-state";
|
|
3
3
|
import { ScanNowButton } from "@/components/scan-now-button";
|
|
4
4
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import dynamic from "next/dynamic";
|
|
4
|
+
import { ChartSkeleton } from "@/components/charts/skeleton";
|
|
5
|
+
|
|
6
|
+
export const RankBarChart = dynamic(
|
|
7
|
+
() => import("./rank-bar-chart").then((m) => m.RankBarChart),
|
|
8
|
+
{ ssr: false, loading: () => <ChartSkeleton heightClass="h-60" label="Loading chart…" /> }
|
|
9
|
+
);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
type ChartSkeletonProps = {
|
|
2
|
+
heightClass?: string;
|
|
3
|
+
label?: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function ChartSkeleton({ heightClass = "h-64", label = "Loading chart…" }: ChartSkeletonProps) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
className={`flex items-center justify-center rounded-md border border-dashed border-muted-foreground/30 bg-muted/30 text-xs text-muted-foreground ${heightClass}`}
|
|
10
|
+
role="status"
|
|
11
|
+
aria-live="polite"
|
|
12
|
+
>
|
|
13
|
+
{label}
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import dynamic from "next/dynamic";
|
|
4
|
+
import { ChartSkeleton } from "@/components/charts/skeleton";
|
|
5
|
+
|
|
6
|
+
export const TrendSection = dynamic(
|
|
7
|
+
() => import("./trend-section").then((m) => m.TrendSection),
|
|
8
|
+
{ ssr: false, loading: () => <ChartSkeleton heightClass="h-72" label="Loading trend chart…" /> }
|
|
9
|
+
);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import Link from "next/link";
|
|
2
|
-
import { RankBarChart } from "@/components/charts/rank-bar-chart";
|
|
2
|
+
import { RankBarChart } from "@/components/charts/rank-bar-chart-lazy";
|
|
3
3
|
import { Badge } from "@/components/ui/badge";
|
|
4
4
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
|
5
5
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
type SectionSkeletonProps = {
|
|
2
|
+
heightClass?: string;
|
|
3
|
+
label: string;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
export function OverviewSectionSkeleton({ heightClass = "h-48", label }: SectionSkeletonProps) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
className={`flex items-center justify-center rounded-md border border-dashed border-muted-foreground/30 bg-muted/30 text-xs text-muted-foreground ${heightClass}`}
|
|
10
|
+
role="status"
|
|
11
|
+
aria-live="polite"
|
|
12
|
+
>
|
|
13
|
+
{label}
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function OverviewPrimarySkeleton() {
|
|
19
|
+
return (
|
|
20
|
+
<div className="space-y-6">
|
|
21
|
+
<OverviewSectionSkeleton heightClass="h-20" label="Loading usage pulse…" />
|
|
22
|
+
<OverviewSectionSkeleton heightClass="h-36" label="Loading summary cards…" />
|
|
23
|
+
<OverviewSectionSkeleton heightClass="h-72" label="Loading trend chart…" />
|
|
24
|
+
</div>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function OverviewRepairSkeleton() {
|
|
29
|
+
return (
|
|
30
|
+
<div className="space-y-6">
|
|
31
|
+
<OverviewSectionSkeleton heightClass="h-24" label="Loading review status…" />
|
|
32
|
+
<OverviewSectionSkeleton heightClass="h-40" label="Loading repair items…" />
|
|
33
|
+
</div>
|
|
34
|
+
);
|
|
35
|
+
}
|
package/dist/runtime/agent.mjs
CHANGED
|
@@ -655,6 +655,11 @@ var init_client = __esm({
|
|
|
655
655
|
dbPath = process.env.TOKENTRACE_DB ?? databaseUrlPath(process.env.DATABASE_URL) ?? defaultDbPath;
|
|
656
656
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
657
657
|
sqlite = new Database(dbPath);
|
|
658
|
+
sqlite.pragma("journal_mode = WAL");
|
|
659
|
+
sqlite.pragma("synchronous = NORMAL");
|
|
660
|
+
sqlite.pragma("temp_store = MEMORY");
|
|
661
|
+
sqlite.pragma("cache_size = -65536");
|
|
662
|
+
sqlite.pragma("mmap_size = 268435456");
|
|
658
663
|
sqlite.pragma("busy_timeout = 10000");
|
|
659
664
|
sqlite.pragma("foreign_keys = ON");
|
|
660
665
|
registerSqliteFunctions(sqlite);
|
|
@@ -765,6 +770,24 @@ var init_agent_actions = __esm({
|
|
|
765
770
|
}
|
|
766
771
|
});
|
|
767
772
|
|
|
773
|
+
// src/db/prepared.ts
|
|
774
|
+
function prepareCached(sql2) {
|
|
775
|
+
let stmt = cache.get(sql2);
|
|
776
|
+
if (!stmt) {
|
|
777
|
+
stmt = sqlite.prepare(sql2);
|
|
778
|
+
cache.set(sql2, stmt);
|
|
779
|
+
}
|
|
780
|
+
return stmt;
|
|
781
|
+
}
|
|
782
|
+
var cache;
|
|
783
|
+
var init_prepared = __esm({
|
|
784
|
+
"src/db/prepared.ts"() {
|
|
785
|
+
"use strict";
|
|
786
|
+
init_client();
|
|
787
|
+
cache = /* @__PURE__ */ new Map();
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
|
|
768
791
|
// src/lib/analytics-query-helpers.ts
|
|
769
792
|
function number(value) {
|
|
770
793
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
@@ -822,7 +845,7 @@ function parseJson(value, fallback) {
|
|
|
822
845
|
}
|
|
823
846
|
}
|
|
824
847
|
function rows(sql2, ...params) {
|
|
825
|
-
return
|
|
848
|
+
return prepareCached(sql2).all(...params);
|
|
826
849
|
}
|
|
827
850
|
function withQuery(path3, params) {
|
|
828
851
|
const query = new URLSearchParams();
|
|
@@ -877,7 +900,7 @@ function timestampJoinCondition(filters = {}, alias = "i") {
|
|
|
877
900
|
var init_analytics_query_helpers = __esm({
|
|
878
901
|
"src/lib/analytics-query-helpers.ts"() {
|
|
879
902
|
"use strict";
|
|
880
|
-
|
|
903
|
+
init_prepared();
|
|
881
904
|
}
|
|
882
905
|
});
|
|
883
906
|
|
|
@@ -3419,7 +3442,7 @@ var init_repair_actions = __esm({
|
|
|
3419
3442
|
|
|
3420
3443
|
// src/lib/unknown-cost-repair/suggestions.ts
|
|
3421
3444
|
function rows2(sql2, ...params) {
|
|
3422
|
-
return
|
|
3445
|
+
return prepareCached(sql2).all(...params);
|
|
3423
3446
|
}
|
|
3424
3447
|
function buildPricedModelLookup() {
|
|
3425
3448
|
const pricedRows = rows2(
|
|
@@ -3493,7 +3516,7 @@ function aliasSuggestion({
|
|
|
3493
3516
|
var init_suggestions = __esm({
|
|
3494
3517
|
"src/lib/unknown-cost-repair/suggestions.ts"() {
|
|
3495
3518
|
"use strict";
|
|
3496
|
-
|
|
3519
|
+
init_prepared();
|
|
3497
3520
|
init_model_aliases();
|
|
3498
3521
|
}
|
|
3499
3522
|
});
|
|
@@ -3503,7 +3526,7 @@ function number3(value) {
|
|
|
3503
3526
|
return Number(value ?? 0);
|
|
3504
3527
|
}
|
|
3505
3528
|
function rows3(sql2, ...params) {
|
|
3506
|
-
return
|
|
3529
|
+
return prepareCached(sql2).all(...params);
|
|
3507
3530
|
}
|
|
3508
3531
|
function interactionDateFilter(filters = {}, alias = "i") {
|
|
3509
3532
|
const clauses = [];
|
|
@@ -3691,7 +3714,7 @@ function buildUnknownCostRepairWorkbench(filters = {}, options = {}) {
|
|
|
3691
3714
|
var init_workbench = __esm({
|
|
3692
3715
|
"src/lib/unknown-cost-repair/workbench.ts"() {
|
|
3693
3716
|
"use strict";
|
|
3694
|
-
|
|
3717
|
+
init_prepared();
|
|
3695
3718
|
init_repair_actions();
|
|
3696
3719
|
init_keys();
|
|
3697
3720
|
init_reviews();
|
|
@@ -628,6 +628,11 @@ function databaseUrlPath(value) {
|
|
|
628
628
|
var dbPath = process.env.TOKENTRACE_DB ?? databaseUrlPath(process.env.DATABASE_URL) ?? defaultDbPath;
|
|
629
629
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
630
630
|
var sqlite = new Database(dbPath);
|
|
631
|
+
sqlite.pragma("journal_mode = WAL");
|
|
632
|
+
sqlite.pragma("synchronous = NORMAL");
|
|
633
|
+
sqlite.pragma("temp_store = MEMORY");
|
|
634
|
+
sqlite.pragma("cache_size = -65536");
|
|
635
|
+
sqlite.pragma("mmap_size = 268435456");
|
|
631
636
|
sqlite.pragma("busy_timeout = 10000");
|
|
632
637
|
sqlite.pragma("foreign_keys = ON");
|
|
633
638
|
registerSqliteFunctions(sqlite);
|
package/dist/runtime/db-seed.mjs
CHANGED
|
@@ -628,6 +628,11 @@ function databaseUrlPath(value) {
|
|
|
628
628
|
var dbPath = process.env.TOKENTRACE_DB ?? databaseUrlPath(process.env.DATABASE_URL) ?? defaultDbPath;
|
|
629
629
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
630
630
|
var sqlite = new Database(dbPath);
|
|
631
|
+
sqlite.pragma("journal_mode = WAL");
|
|
632
|
+
sqlite.pragma("synchronous = NORMAL");
|
|
633
|
+
sqlite.pragma("temp_store = MEMORY");
|
|
634
|
+
sqlite.pragma("cache_size = -65536");
|
|
635
|
+
sqlite.pragma("mmap_size = 268435456");
|
|
631
636
|
sqlite.pragma("busy_timeout = 10000");
|
|
632
637
|
sqlite.pragma("foreign_keys = ON");
|
|
633
638
|
registerSqliteFunctions(sqlite);
|
package/dist/runtime/digest.mjs
CHANGED
|
@@ -655,6 +655,11 @@ var init_client = __esm({
|
|
|
655
655
|
dbPath = process.env.TOKENTRACE_DB ?? databaseUrlPath(process.env.DATABASE_URL) ?? defaultDbPath;
|
|
656
656
|
fs.mkdirSync(path.dirname(dbPath), { recursive: true });
|
|
657
657
|
sqlite = new Database(dbPath);
|
|
658
|
+
sqlite.pragma("journal_mode = WAL");
|
|
659
|
+
sqlite.pragma("synchronous = NORMAL");
|
|
660
|
+
sqlite.pragma("temp_store = MEMORY");
|
|
661
|
+
sqlite.pragma("cache_size = -65536");
|
|
662
|
+
sqlite.pragma("mmap_size = 268435456");
|
|
658
663
|
sqlite.pragma("busy_timeout = 10000");
|
|
659
664
|
sqlite.pragma("foreign_keys = ON");
|
|
660
665
|
registerSqliteFunctions(sqlite);
|
|
@@ -663,6 +668,24 @@ var init_client = __esm({
|
|
|
663
668
|
}
|
|
664
669
|
});
|
|
665
670
|
|
|
671
|
+
// src/db/prepared.ts
|
|
672
|
+
function prepareCached(sql2) {
|
|
673
|
+
let stmt = cache.get(sql2);
|
|
674
|
+
if (!stmt) {
|
|
675
|
+
stmt = sqlite.prepare(sql2);
|
|
676
|
+
cache.set(sql2, stmt);
|
|
677
|
+
}
|
|
678
|
+
return stmt;
|
|
679
|
+
}
|
|
680
|
+
var cache;
|
|
681
|
+
var init_prepared = __esm({
|
|
682
|
+
"src/db/prepared.ts"() {
|
|
683
|
+
"use strict";
|
|
684
|
+
init_client();
|
|
685
|
+
cache = /* @__PURE__ */ new Map();
|
|
686
|
+
}
|
|
687
|
+
});
|
|
688
|
+
|
|
666
689
|
// src/lib/analytics-query-helpers.ts
|
|
667
690
|
function number(value) {
|
|
668
691
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
@@ -720,7 +743,7 @@ function parseJson(value, fallback) {
|
|
|
720
743
|
}
|
|
721
744
|
}
|
|
722
745
|
function rows(sql2, ...params) {
|
|
723
|
-
return
|
|
746
|
+
return prepareCached(sql2).all(...params);
|
|
724
747
|
}
|
|
725
748
|
function withQuery(path2, params) {
|
|
726
749
|
const query = new URLSearchParams();
|
|
@@ -775,7 +798,7 @@ function timestampJoinCondition(filters = {}, alias = "i") {
|
|
|
775
798
|
var init_analytics_query_helpers = __esm({
|
|
776
799
|
"src/lib/analytics-query-helpers.ts"() {
|
|
777
800
|
"use strict";
|
|
778
|
-
|
|
801
|
+
init_prepared();
|
|
779
802
|
}
|
|
780
803
|
});
|
|
781
804
|
|