@diogonzafe/tokenwatch 0.2.1 → 0.4.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/README.md CHANGED
@@ -34,17 +34,44 @@ import { createTracker } from '@diogonzafe/tokenwatch'
34
34
  const tracker = createTracker({
35
35
  // All fields are optional
36
36
  storage: 'memory', // 'memory' (default) | 'sqlite' | IStorage instance
37
- alertThreshold: 1.00, // USD — fires webhookUrl when exceeded
37
+ alertThreshold: 1.00, // USD — fires webhookUrl when total cost exceeded
38
38
  webhookUrl: 'https://...', // Discord / Slack webhook
39
39
  syncPrices: true, // fetch fresh prices from GitHub (default: true)
40
+ warnIfStaleAfterHours: 72, // warn if prices are older than N hours (0 = disable)
40
41
  customPrices: {
41
42
  'my-model': { input: 0.50, output: 1.50, maxInputTokens: 32000 } // USD per 1M tokens
42
- }
43
+ },
44
+ budgets: {
45
+ perUser: { threshold: 1.00, webhookUrl: 'https://...' }, // per-user alert
46
+ perSession: { threshold: 0.10, webhookUrl: 'https://...' }, // per-session alert
47
+ },
48
+ suggestions: true, // log hints for cheaper models in same family (>50% savings)
43
49
  })
44
50
  ```
45
51
 
46
52
  ---
47
53
 
54
+ ## Lazy Initialization
55
+
56
+ For module-level singletons where the tracker needs to be imported before `createTracker()` can run (e.g. shared modules, Jest test environments):
57
+
58
+ ```ts
59
+ import { createLazyTracker } from '@diogonzafe/tokenwatch'
60
+
61
+ // Safe to import anywhere — all methods are no-ops until init() is called
62
+ export const tracker = createLazyTracker()
63
+
64
+ // At app startup (e.g. index.ts / server.ts):
65
+ tracker.init({ storage: 'sqlite', syncPrices: true })
66
+
67
+ // In tests — never call init(), track() silently no-ops:
68
+ tracker.track({ model: 'gpt-4o', inputTokens: 100, outputTokens: 50 }) // does nothing
69
+ ```
70
+
71
+ `init()` may only be called once — a second call throws. A failed `init()` (e.g. invalid config) leaves the tracker in no-op mode. `LazyTracker` satisfies the `Tracker` interface, so it can be used anywhere a `Tracker` is expected.
72
+
73
+ ---
74
+
48
75
  ## OpenAI
49
76
 
50
77
  ```ts
@@ -159,6 +186,111 @@ const res = await deepseek.chat.completions.create({
159
186
 
160
187
  ---
161
188
 
189
+ ## Agent Frameworks
190
+
191
+ Frameworks like **Mastra**, **Vercel AI SDK**, **LlamaIndex**, and **LangChain** use their own internal LLM abstractions — they never expose the raw OpenAI/Anthropic client. `wrapOpenAI` and `wrapAnthropic` do not apply. Use `tracker.track()` manually via each framework's usage callback instead.
192
+
193
+ > `tracker.track()` always expects `inputTokens` and `outputTokens`. The field names exposed by each framework differ — see the mappings below.
194
+
195
+ ### Mastra
196
+
197
+ `agent.generate()` and `agent.stream()` expose usage in `onStepFinish`:
198
+
199
+ ```ts
200
+ import { Agent } from '@mastra/core/agent'
201
+ import { createTracker } from '@diogonzafe/tokenwatch'
202
+
203
+ const tracker = createTracker({ storage: 'sqlite' })
204
+
205
+ const agent = new Agent({ model: 'openai/gpt-4o', instructions: '...' })
206
+
207
+ const result = await agent.generate('Hello', {
208
+ onStepFinish: ({ usage }) => {
209
+ tracker.track({
210
+ model: 'gpt-4o',
211
+ inputTokens: usage.promptTokens, // Mastra uses promptTokens
212
+ outputTokens: usage.completionTokens, // Mastra uses completionTokens
213
+ sessionId: 'sess-abc',
214
+ })
215
+ },
216
+ })
217
+ ```
218
+
219
+ ### Vercel AI SDK
220
+
221
+ `streamText` / `generateText` expose usage in `onFinish`. As of `ai` v5, the fields are `inputTokens` / `outputTokens`:
222
+
223
+ ```ts
224
+ import { createOpenAI } from '@ai-sdk/openai'
225
+ import { streamText } from 'ai'
226
+ import { createTracker } from '@diogonzafe/tokenwatch'
227
+
228
+ const tracker = createTracker({ storage: 'sqlite' })
229
+ const openai = createOpenAI({ apiKey: process.env.OPENAI_API_KEY! })
230
+
231
+ await streamText({
232
+ model: openai('gpt-4o'),
233
+ prompt: 'Hello',
234
+ onFinish: ({ usage }) => {
235
+ tracker.track({
236
+ model: 'gpt-4o',
237
+ inputTokens: usage.inputTokens ?? 0,
238
+ outputTokens: usage.outputTokens ?? 0,
239
+ })
240
+ },
241
+ })
242
+ ```
243
+
244
+ For multi-step agents, use `totalUsage` instead of `usage` in `onFinish` to get the aggregate across all steps.
245
+
246
+ ### LlamaIndex TypeScript
247
+
248
+ Use `Settings.callbackManager` to intercept `llm-end` events. The raw OpenAI response is available as `response.raw` with snake_case field names:
249
+
250
+ ```ts
251
+ import { Settings } from 'llamaindex'
252
+ import { createTracker } from '@diogonzafe/tokenwatch'
253
+
254
+ const tracker = createTracker({ storage: 'sqlite' })
255
+
256
+ Settings.callbackManager.on('llm-end', (event) => {
257
+ const raw = event.detail.response.raw as { model?: string; usage?: { prompt_tokens: number; completion_tokens: number } }
258
+ if (raw?.usage) {
259
+ tracker.track({
260
+ model: raw.model ?? 'unknown',
261
+ inputTokens: raw.usage.prompt_tokens, // LlamaIndex exposes snake_case
262
+ outputTokens: raw.usage.completion_tokens,
263
+ })
264
+ }
265
+ })
266
+ ```
267
+
268
+ ### LangChain.js
269
+
270
+ Use the built-in `TokenwatchCallbackHandler` from the `/langchain` sub-path:
271
+
272
+ ```ts
273
+ import { ChatOpenAI } from '@langchain/openai'
274
+ import { createTracker } from '@diogonzafe/tokenwatch'
275
+ import { TokenwatchCallbackHandler } from '@diogonzafe/tokenwatch/langchain'
276
+
277
+ const tracker = createTracker({ storage: 'sqlite' })
278
+ const handler = new TokenwatchCallbackHandler(tracker, {
279
+ defaultModel: 'gpt-4o', // fallback when the response doesn't include the model name
280
+ sessionId: 'sess_abc', // optional — tag all calls from this handler
281
+ userId: 'user_123',
282
+ feature: 'chat',
283
+ })
284
+
285
+ const llm = new ChatOpenAI({ model: 'gpt-4o', callbacks: [handler] })
286
+ ```
287
+
288
+ The handler extracts `promptTokens` / `completionTokens` from `llmOutput.tokenUsage` (non-streaming) and falls back to `estimatedTokenUsage` for streaming calls. No `@langchain/core` compile-time dependency is required in tokenwatch itself — it is an optional peer dependency.
289
+
290
+ > **Note:** This requires `@langchain/core >= 0.1.0` to be installed in your project.
291
+
292
+ ---
293
+
162
294
  ## Reports
163
295
 
164
296
  All report methods are async:
@@ -169,24 +301,36 @@ const report = await tracker.getReport()
169
301
  // totalCostUSD: 0.087,
170
302
  // totalTokens: { input: 24000, output: 6000 },
171
303
  // byModel: {
172
- // 'gpt-4o': { costUSD: 0.062, calls: 5, tokens: { input: 20000, output: 5000, reasoning: 0 } },
173
- // 'o3': { costUSD: 0.041, calls: 1, tokens: { input: 1000, output: 200, reasoning: 800 } },
174
- // 'claude-sonnet-4-6': { costUSD: 0.025, calls: 2, tokens: { input: 4000, output: 1000, reasoning: 0 } }
304
+ // 'gpt-4o': { costUSD: 0.062, calls: 5, tokens: { input: 20000, output: 5000, reasoning: 0, cached: 4000 } },
305
+ // 'o3': { costUSD: 0.041, calls: 1, tokens: { input: 1000, output: 200, reasoning: 800, cached: 0 } },
175
306
  // },
176
307
  // bySession: { 'session_abc': { costUSD: 0.045, calls: 4 } },
177
308
  // byUser: { 'user_123': { costUSD: 0.087, calls: 7 } },
178
309
  // byFeature: { 'chat': { costUSD: 0.062, calls: 5 }, 'rag': { costUSD: 0.025, calls: 3 } },
179
- // period: { from: '2026-04-16T10:00:00Z', to: '2026-04-16T11:00:00Z' }
310
+ // period: { from: '2026-04-16T10:00:00Z', to: '2026-04-16T11:00:00Z' },
311
+ // pricesUpdatedAt: '2026-04-22' // date of the price data in use
180
312
  // }
181
313
 
314
+ // Time-filtered reports
315
+ await tracker.getReport({ last: '24h' }) // last 24 hours
316
+ await tracker.getReport({ last: '7d' }) // last 7 days
317
+ await tracker.getReport({ since: '2026-04-01' }) // since a specific date
318
+ await tracker.getReport({ since: '2026-04-01', until: '2026-04-30' })
319
+
320
+ // Cost forecast — burn rate + projected daily/monthly spend
321
+ const forecast = await tracker.getCostForecast()
322
+ // { burnRatePerHour: 0.043, projectedDailyCostUSD: 1.03, projectedMonthlyCostUSD: 31.20, basedOnHours: 6 }
323
+
324
+ await tracker.getCostForecast({ windowHours: 1 }) // use last 1h for burn rate calculation
325
+
182
326
  tracker.getModelInfo('gpt-4o')
183
- // { input: 2.5, output: 10, maxInputTokens: 128000 }
327
+ // { input: 2.5, output: 10, cachedInput: 1.25, maxInputTokens: 128000 }
184
328
  // Returns null if the model is unknown (synchronous)
185
329
 
186
330
  await tracker.reset() // clear all data
187
331
  await tracker.resetSession('session_abc') // clear one session
188
332
  await tracker.exportJSON() // full report as JSON string
189
- await tracker.exportCSV() // all raw calls as CSV (RFC 4180 — fields with commas/quotes are escaped)
333
+ await tracker.exportCSV() // all raw calls as CSV (RFC 4180)
190
334
  ```
191
335
 
192
336
  ---
@@ -211,10 +355,10 @@ Prices are in **USD per 1 million tokens**.
211
355
 
212
356
  ```json
213
357
  {
214
- "gpt-4o": { "input": 2.50, "output": 10.00, "maxInputTokens": 128000 },
215
- "claude-sonnet-4-6": { "input": 3.00, "output": 15.00, "maxInputTokens": 1000000 },
216
- "gemini-2.5-pro": { "input": 1.25, "output": 10.00, "maxInputTokens": 1048576 },
217
- "deepseek-chat": { "input": 0.28, "output": 0.42, "maxInputTokens": 131072 }
358
+ "gpt-4o": { "input": 2.50, "output": 10.00, "cachedInput": 1.25, "maxInputTokens": 128000 },
359
+ "claude-sonnet-4-6": { "input": 3.00, "output": 15.00, "cachedInput": 0.30, "cacheCreationInput": 3.75, "maxInputTokens": 1000000 },
360
+ "gemini-2.5-pro": { "input": 1.25, "output": 10.00, "maxInputTokens": 1048576 },
361
+ "deepseek-chat": { "input": 0.28, "output": 0.42, "maxInputTokens": 131072 }
218
362
  }
219
363
  ```
220
364
 
@@ -316,8 +460,45 @@ const tracker = createTracker({ storage: new RedisStorage() })
316
460
 
317
461
  ---
318
462
 
463
+ ## Prompt Caching
464
+
465
+ OpenAI and Anthropic offer discounted pricing for cached prompt tokens. tokenwatch tracks these automatically — no changes needed at the call site.
466
+
467
+ **OpenAI** — cached reads billed at 50% of input price:
468
+ ```ts
469
+ // prompt_tokens_details.cached_tokens is extracted automatically
470
+ const res = await openai.chat.completions.create({
471
+ model: 'gpt-4o',
472
+ messages: [{ role: 'user', content: '...' }],
473
+ })
474
+ // report.byModel['gpt-4o'].tokens.cached shows how many tokens were served from cache
475
+ ```
476
+
477
+ **Anthropic** — cache reads at 10% of input price, cache creation at 125%:
478
+ ```ts
479
+ const res = await anthropic.messages.create({
480
+ model: 'claude-sonnet-4-6',
481
+ max_tokens: 1024,
482
+ messages: [{ role: 'user', content: '...' }],
483
+ // cache_read_input_tokens and cache_creation_input_tokens extracted from usage automatically
484
+ })
485
+ ```
486
+
487
+ Cached token prices are included in `prices.json` for all models that support caching. You can also override them:
488
+ ```ts
489
+ const tracker = createTracker({
490
+ customPrices: {
491
+ 'my-model': { input: 2.50, output: 10.00, cachedInput: 1.25, cacheCreationInput: 3.13 }
492
+ }
493
+ })
494
+ ```
495
+
496
+ ---
497
+
319
498
  ## Alerts & Webhooks
320
499
 
500
+ ### Global threshold
501
+
321
502
  ```ts
322
503
  const tracker = createTracker({
323
504
  alertThreshold: 5.00, // USD
@@ -331,6 +512,36 @@ Webhook payload:
331
512
  { "text": "[tokenwatch] Alert: total cost reached $5.0012 USD (threshold: $5)" }
332
513
  ```
333
514
 
515
+ ### Per-user and per-session budgets
516
+
517
+ ```ts
518
+ const tracker = createTracker({
519
+ budgets: {
520
+ perUser: {
521
+ threshold: 1.00,
522
+ webhookUrl: 'https://hooks.slack.com/...',
523
+ mode: 'once', // default — fires once per user; use 'always' to fire on every call that exceeds
524
+ },
525
+ perSession: {
526
+ threshold: 0.10,
527
+ webhookUrl: 'https://hooks.slack.com/...',
528
+ },
529
+ },
530
+ })
531
+
532
+ await openai.chat.completions.create({
533
+ model: 'gpt-4o',
534
+ messages: [...],
535
+ __userId: 'user_123', // required for perUser alert to fire
536
+ __sessionId: 'sess_abc', // required for perSession alert to fire
537
+ })
538
+ ```
539
+
540
+ Budget webhook payload:
541
+ ```json
542
+ { "text": "[tokenwatch] Budget alert: user \"user_123\" reached $1.0031 USD (threshold: $1)" }
543
+ ```
544
+
334
545
  ---
335
546
 
336
547
  ## CLI
@@ -405,7 +616,7 @@ type MyParams = { model: string; messages: Message[] } & TrackingMeta
405
616
 
406
617
  - `__sessionId`, `__userId`, and `__feature` are **stripped before** the request reaches the API
407
618
  - The response object returned is **identical** to the original SDK response
408
- - `track()` is **synchronous and non-blocking** — zero latency added to API calls
619
+ - `track()` is **synchronous and non-blocking** — negligible sub-millisecond overhead; no proxy server or network hop
409
620
  - If the API call **fails**, no cost is recorded and the original error is re-thrown unchanged
410
621
  - Streaming is fully supported — usage is accumulated from the final stream event
411
622
  - Database writes from `record()` are **fire-and-forget** — a storage failure never interrupts your API call
package/dist/adapters.cjs CHANGED
@@ -33,41 +33,47 @@ var PostgresStorage = class {
33
33
  }
34
34
  client;
35
35
  /** Creates the `tokenwatch_usage` table if it does not already exist.
36
- * Also adds new columns for databases created before v0.2.0. */
36
+ * Also adds new columns for databases created before v0.2.0 / v0.3.0. */
37
37
  async migrate() {
38
38
  await this.client.query(`
39
39
  CREATE TABLE IF NOT EXISTS tokenwatch_usage (
40
- id BIGSERIAL PRIMARY KEY,
41
- model TEXT NOT NULL,
42
- input_tokens INTEGER NOT NULL,
43
- output_tokens INTEGER NOT NULL,
44
- reasoning_tokens INTEGER NOT NULL DEFAULT 0,
45
- cost_usd NUMERIC NOT NULL,
46
- session_id TEXT,
47
- user_id TEXT,
48
- feature TEXT,
49
- timestamp TIMESTAMPTZ NOT NULL
40
+ id BIGSERIAL PRIMARY KEY,
41
+ model TEXT NOT NULL,
42
+ input_tokens INTEGER NOT NULL,
43
+ output_tokens INTEGER NOT NULL,
44
+ reasoning_tokens INTEGER NOT NULL DEFAULT 0,
45
+ cached_tokens INTEGER NOT NULL DEFAULT 0,
46
+ cache_creation_tokens INTEGER NOT NULL DEFAULT 0,
47
+ cost_usd NUMERIC NOT NULL,
48
+ session_id TEXT,
49
+ user_id TEXT,
50
+ feature TEXT,
51
+ timestamp TIMESTAMPTZ NOT NULL
50
52
  )
51
53
  `);
52
- await this.client.query(`
53
- ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0
54
- `).catch(() => {
55
- });
56
- await this.client.query(`
57
- ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT
58
- `).catch(() => {
59
- });
54
+ for (const col of [
55
+ "ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0",
56
+ "ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT",
57
+ "ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cached_tokens INTEGER NOT NULL DEFAULT 0",
58
+ "ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cache_creation_tokens INTEGER NOT NULL DEFAULT 0"
59
+ ]) {
60
+ await this.client.query(col).catch(() => {
61
+ });
62
+ }
60
63
  }
61
64
  record(entry) {
62
65
  this.client.query(
63
66
  `INSERT INTO tokenwatch_usage
64
- (model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)
65
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
67
+ (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,
68
+ cost_usd, session_id, user_id, feature, timestamp)
69
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,
66
70
  [
67
71
  entry.model,
68
72
  entry.inputTokens,
69
73
  entry.outputTokens,
70
74
  entry.reasoningTokens ?? 0,
75
+ entry.cachedTokens ?? 0,
76
+ entry.cacheCreationTokens ?? 0,
71
77
  entry.costUSD,
72
78
  entry.sessionId ?? null,
73
79
  entry.userId ?? null,
@@ -96,11 +102,15 @@ var PostgresStorage = class {
96
102
  };
97
103
  function rowToEntry(r) {
98
104
  const reasoningTokens = r["reasoning_tokens"] ?? 0;
105
+ const cachedTokens = r["cached_tokens"] ?? 0;
106
+ const cacheCreationTokens = r["cache_creation_tokens"] ?? 0;
99
107
  return {
100
108
  model: r["model"],
101
109
  inputTokens: r["input_tokens"],
102
110
  outputTokens: r["output_tokens"],
103
111
  ...reasoningTokens > 0 && { reasoningTokens },
112
+ ...cachedTokens > 0 && { cachedTokens },
113
+ ...cacheCreationTokens > 0 && { cacheCreationTokens },
104
114
  costUSD: Number(r["cost_usd"]),
105
115
  ...r["session_id"] != null && { sessionId: r["session_id"] },
106
116
  ...r["user_id"] != null && { userId: r["user_id"] },
@@ -116,39 +126,46 @@ var MySQLStorage = class {
116
126
  }
117
127
  client;
118
128
  /** Creates the `tokenwatch_usage` table if it does not already exist.
119
- * Also adds new columns for databases created before v0.2.0. */
129
+ * Also adds new columns for databases created before v0.2.0 / v0.3.0. */
120
130
  async migrate() {
121
131
  await this.client.execute(`
122
132
  CREATE TABLE IF NOT EXISTS tokenwatch_usage (
123
- id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
124
- model VARCHAR(255) NOT NULL,
125
- input_tokens INT NOT NULL,
126
- output_tokens INT NOT NULL,
127
- reasoning_tokens INT NOT NULL DEFAULT 0,
128
- cost_usd DECIMAL(18,8) NOT NULL,
129
- session_id VARCHAR(255),
130
- user_id VARCHAR(255),
131
- feature VARCHAR(255),
132
- timestamp DATETIME(3) NOT NULL
133
+ id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
134
+ model VARCHAR(255) NOT NULL,
135
+ input_tokens INT NOT NULL,
136
+ output_tokens INT NOT NULL,
137
+ reasoning_tokens INT NOT NULL DEFAULT 0,
138
+ cached_tokens INT NOT NULL DEFAULT 0,
139
+ cache_creation_tokens INT NOT NULL DEFAULT 0,
140
+ cost_usd DECIMAL(18,8) NOT NULL,
141
+ session_id VARCHAR(255),
142
+ user_id VARCHAR(255),
143
+ feature VARCHAR(255),
144
+ timestamp DATETIME(3) NOT NULL
133
145
  )
134
146
  `);
135
147
  await this.client.execute(`
136
148
  ALTER TABLE tokenwatch_usage
137
- ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,
138
- ADD COLUMN IF NOT EXISTS feature VARCHAR(255)
149
+ ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,
150
+ ADD COLUMN IF NOT EXISTS feature VARCHAR(255),
151
+ ADD COLUMN IF NOT EXISTS cached_tokens INT NOT NULL DEFAULT 0,
152
+ ADD COLUMN IF NOT EXISTS cache_creation_tokens INT NOT NULL DEFAULT 0
139
153
  `).catch(() => {
140
154
  });
141
155
  }
142
156
  record(entry) {
143
157
  this.client.execute(
144
158
  `INSERT INTO tokenwatch_usage
145
- (model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)
146
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
159
+ (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,
160
+ cost_usd, session_id, user_id, feature, timestamp)
161
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
147
162
  [
148
163
  entry.model,
149
164
  entry.inputTokens,
150
165
  entry.outputTokens,
151
166
  entry.reasoningTokens ?? 0,
167
+ entry.cachedTokens ?? 0,
168
+ entry.cacheCreationTokens ?? 0,
152
169
  entry.costUSD,
153
170
  entry.sessionId ?? null,
154
171
  entry.userId ?? null,
@@ -177,11 +194,15 @@ var MySQLStorage = class {
177
194
  };
178
195
  function rowToEntry2(r) {
179
196
  const reasoningTokens = r["reasoning_tokens"] ?? 0;
197
+ const cachedTokens = r["cached_tokens"] ?? 0;
198
+ const cacheCreationTokens = r["cache_creation_tokens"] ?? 0;
180
199
  return {
181
200
  model: r["model"],
182
201
  inputTokens: r["input_tokens"],
183
202
  outputTokens: r["output_tokens"],
184
203
  ...reasoningTokens > 0 && { reasoningTokens },
204
+ ...cachedTokens > 0 && { cachedTokens },
205
+ ...cacheCreationTokens > 0 && { cacheCreationTokens },
185
206
  costUSD: Number(r["cost_usd"]),
186
207
  ...r["session_id"] != null && { sessionId: r["session_id"] },
187
208
  ...r["user_id"] != null && { userId: r["user_id"] },
@@ -210,6 +231,8 @@ var MongoStorage = class {
210
231
  inputTokens: entry.inputTokens,
211
232
  outputTokens: entry.outputTokens,
212
233
  ...entry.reasoningTokens !== void 0 && { reasoningTokens: entry.reasoningTokens },
234
+ ...entry.cachedTokens !== void 0 && { cachedTokens: entry.cachedTokens },
235
+ ...entry.cacheCreationTokens !== void 0 && { cacheCreationTokens: entry.cacheCreationTokens },
213
236
  costUSD: entry.costUSD,
214
237
  sessionId: entry.sessionId ?? null,
215
238
  userId: entry.userId ?? null,
@@ -236,6 +259,8 @@ function docToEntry(doc) {
236
259
  inputTokens: doc.inputTokens,
237
260
  outputTokens: doc.outputTokens,
238
261
  ...doc.reasoningTokens != null && doc.reasoningTokens > 0 && { reasoningTokens: doc.reasoningTokens },
262
+ ...doc.cachedTokens != null && doc.cachedTokens > 0 && { cachedTokens: doc.cachedTokens },
263
+ ...doc.cacheCreationTokens != null && doc.cacheCreationTokens > 0 && { cacheCreationTokens: doc.cacheCreationTokens },
239
264
  costUSD: doc.costUSD,
240
265
  ...doc.sessionId != null && { sessionId: doc.sessionId },
241
266
  ...doc.userId != null && { userId: doc.userId },
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/adapters/index.ts","../src/adapters/postgres.ts","../src/adapters/mysql.ts","../src/adapters/mongodb.ts"],"sourcesContent":["export { PostgresStorage } from './postgres.js'\nexport { MySQLStorage } from './mysql.js'\nexport { MongoStorage } from './mongodb.js'\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for PostgreSQL using the `pg` driver.\n *\n * Install peer dep: npm install pg\n * Types (optional): npm install -D @types/pg\n *\n * @example\n * ```ts\n * import { Pool } from 'pg'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL })\n * const storage = new PostgresStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `pg` installed\ninterface QueryClient {\n query(sql: string, values?: unknown[]): Promise<{ rows: unknown[] }>\n}\n\nexport class PostgresStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0. */\n async migrate(): Promise<void> {\n await this.client.query(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGSERIAL PRIMARY KEY,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n reasoning_tokens INTEGER NOT NULL DEFAULT 0,\n cost_usd NUMERIC NOT NULL,\n session_id TEXT,\n user_id TEXT,\n feature TEXT,\n timestamp TIMESTAMPTZ NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0\n await this.client.query(`\n ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0\n `).catch(() => { /* column already exists in older Postgres versions that don't support IF NOT EXISTS */ })\n await this.client.query(`\n ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT\n `).catch(() => { /* same */ })\n }\n\n record(entry: UsageEntry): void {\n this.client\n .query(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] PostgresStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const result = await this.client.query(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (result.rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.query('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.query(\n 'DELETE FROM tokenwatch_usage WHERE session_id = $1',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MySQL / MariaDB using the `mysql2` driver.\n *\n * Install peer dep: npm install mysql2\n *\n * @example\n * ```ts\n * import mysql from 'mysql2/promise'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = mysql.createPool({ uri: process.env.MYSQL_URL })\n * const storage = new MySQLStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural type so the adapter compiles without `mysql2` installed\ninterface QueryClient {\n execute(sql: string, values?: unknown[]): Promise<[unknown]>\n}\n\nexport class MySQLStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0. */\n async migrate(): Promise<void> {\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n model VARCHAR(255) NOT NULL,\n input_tokens INT NOT NULL,\n output_tokens INT NOT NULL,\n reasoning_tokens INT NOT NULL DEFAULT 0,\n cost_usd DECIMAL(18,8) NOT NULL,\n session_id VARCHAR(255),\n user_id VARCHAR(255),\n feature VARCHAR(255),\n timestamp DATETIME(3) NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0\n await this.client.execute(`\n ALTER TABLE tokenwatch_usage\n ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS feature VARCHAR(255)\n `).catch(() => { /* MySQL < 8.0 may not support IF NOT EXISTS — ignore if columns already exist */ })\n }\n\n record(entry: UsageEntry): void {\n this.client\n .execute(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cost_usd, session_id, user_id, feature, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MySQLStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const [rows] = await this.client.execute(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.execute('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.execute(\n 'DELETE FROM tokenwatch_usage WHERE session_id = ?',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MongoDB using the official `mongodb` driver.\n *\n * Install peer dep: npm install mongodb\n *\n * @example\n * ```ts\n * import { MongoClient } from 'mongodb'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const client = new MongoClient(process.env.MONGO_URL!)\n * await client.connect()\n *\n * const storage = new MongoStorage(client.db('myapp'))\n * const tracker = createTracker({ storage })\n * ```\n *\n * Recommended index (run once at startup):\n * ```ts\n * await storage.createIndexes()\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `mongodb` installed\ninterface MongoDocument {\n _id?: unknown\n model: string\n inputTokens: number\n outputTokens: number\n reasoningTokens?: number\n costUSD: number\n sessionId?: string | null\n userId?: string | null\n feature?: string | null\n timestamp: string\n}\n\ninterface MongoCursor {\n sort(sort: Record<string, unknown>): MongoCursor\n toArray(): Promise<MongoDocument[]>\n}\n\ninterface Collection {\n insertOne(doc: MongoDocument): Promise<unknown>\n find(filter: Record<string, unknown>): MongoCursor\n deleteMany(filter: Record<string, unknown>): Promise<unknown>\n createIndex(index: Record<string, unknown>): Promise<unknown>\n}\n\ninterface Database {\n collection(name: string): Collection\n}\n\nconst COLLECTION = 'tokenwatch_usage'\n\nexport class MongoStorage implements IStorage {\n private readonly col: Collection\n\n constructor(db: Database) {\n this.col = db.collection(COLLECTION)\n }\n\n /** Creates recommended indexes for query performance. Call once at startup. */\n async createIndexes(): Promise<void> {\n await this.col.createIndex({ timestamp: 1 })\n await this.col.createIndex({ sessionId: 1 })\n await this.col.createIndex({ userId: 1 })\n await this.col.createIndex({ model: 1 })\n }\n\n record(entry: UsageEntry): void {\n this.col\n .insertOne({\n model: entry.model,\n inputTokens: entry.inputTokens,\n outputTokens: entry.outputTokens,\n ...(entry.reasoningTokens !== undefined && { reasoningTokens: entry.reasoningTokens }),\n costUSD: entry.costUSD,\n sessionId: entry.sessionId ?? null,\n userId: entry.userId ?? null,\n ...(entry.feature !== undefined && { feature: entry.feature }),\n timestamp: entry.timestamp,\n })\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MongoStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const docs = await this.col.find({}).sort({ timestamp: 1 }).toArray()\n return docs.map(docToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.col.deleteMany({})\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.col.deleteMany({ sessionId })\n }\n}\n\nfunction docToEntry(doc: MongoDocument): UsageEntry {\n return {\n model: doc.model,\n inputTokens: doc.inputTokens,\n outputTokens: doc.outputTokens,\n ...(doc.reasoningTokens != null && doc.reasoningTokens > 0 && { reasoningTokens: doc.reasoningTokens }),\n costUSD: doc.costUSD,\n ...(doc.sessionId != null && { sessionId: doc.sessionId }),\n ...(doc.userId != null && { userId: doc.userId }),\n ...(doc.feature != null && { feature: doc.feature }),\n timestamp: doc.timestamp,\n }\n}\n"],"mappings":"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","names":["rowToEntry"]}
1
+ {"version":3,"sources":["../src/adapters/index.ts","../src/adapters/postgres.ts","../src/adapters/mysql.ts","../src/adapters/mongodb.ts"],"sourcesContent":["export { PostgresStorage } from './postgres.js'\nexport { MySQLStorage } from './mysql.js'\nexport { MongoStorage } from './mongodb.js'\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for PostgreSQL using the `pg` driver.\n *\n * Install peer dep: npm install pg\n * Types (optional): npm install -D @types/pg\n *\n * @example\n * ```ts\n * import { Pool } from 'pg'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL })\n * const storage = new PostgresStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `pg` installed\ninterface QueryClient {\n query(sql: string, values?: unknown[]): Promise<{ rows: unknown[] }>\n}\n\nexport class PostgresStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0 / v0.3.0. */\n async migrate(): Promise<void> {\n await this.client.query(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGSERIAL PRIMARY KEY,\n model TEXT NOT NULL,\n input_tokens INTEGER NOT NULL,\n output_tokens INTEGER NOT NULL,\n reasoning_tokens INTEGER NOT NULL DEFAULT 0,\n cached_tokens INTEGER NOT NULL DEFAULT 0,\n cache_creation_tokens INTEGER NOT NULL DEFAULT 0,\n cost_usd NUMERIC NOT NULL,\n session_id TEXT,\n user_id TEXT,\n feature TEXT,\n timestamp TIMESTAMPTZ NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0 / v0.3.0\n for (const col of [\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS feature TEXT',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cached_tokens INTEGER NOT NULL DEFAULT 0',\n 'ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS cache_creation_tokens INTEGER NOT NULL DEFAULT 0',\n ]) {\n await this.client.query(col).catch(() => { /* column already exists */ })\n }\n }\n\n record(entry: UsageEntry): void {\n this.client\n .query(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,\n cost_usd, session_id, user_id, feature, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.cachedTokens ?? 0,\n entry.cacheCreationTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] PostgresStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const result = await this.client.query(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (result.rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.query('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.query(\n 'DELETE FROM tokenwatch_usage WHERE session_id = $1',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n const cachedTokens = (r['cached_tokens'] as number | null) ?? 0\n const cacheCreationTokens = (r['cache_creation_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n ...(cachedTokens > 0 && { cachedTokens }),\n ...(cacheCreationTokens > 0 && { cacheCreationTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MySQL / MariaDB using the `mysql2` driver.\n *\n * Install peer dep: npm install mysql2\n *\n * @example\n * ```ts\n * import mysql from 'mysql2/promise'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const pool = mysql.createPool({ uri: process.env.MYSQL_URL })\n * const storage = new MySQLStorage(pool)\n * await storage.migrate() // create table if it doesn't exist\n *\n * const tracker = createTracker({ storage })\n * ```\n */\n\n// Minimal structural type so the adapter compiles without `mysql2` installed\ninterface QueryClient {\n execute(sql: string, values?: unknown[]): Promise<[unknown]>\n}\n\nexport class MySQLStorage implements IStorage {\n constructor(private readonly client: QueryClient) {}\n\n /** Creates the `tokenwatch_usage` table if it does not already exist.\n * Also adds new columns for databases created before v0.2.0 / v0.3.0. */\n async migrate(): Promise<void> {\n await this.client.execute(`\n CREATE TABLE IF NOT EXISTS tokenwatch_usage (\n id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,\n model VARCHAR(255) NOT NULL,\n input_tokens INT NOT NULL,\n output_tokens INT NOT NULL,\n reasoning_tokens INT NOT NULL DEFAULT 0,\n cached_tokens INT NOT NULL DEFAULT 0,\n cache_creation_tokens INT NOT NULL DEFAULT 0,\n cost_usd DECIMAL(18,8) NOT NULL,\n session_id VARCHAR(255),\n user_id VARCHAR(255),\n feature VARCHAR(255),\n timestamp DATETIME(3) NOT NULL\n )\n `)\n // Incremental migrations for databases created before v0.2.0 / v0.3.0\n await this.client.execute(`\n ALTER TABLE tokenwatch_usage\n ADD COLUMN IF NOT EXISTS reasoning_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS feature VARCHAR(255),\n ADD COLUMN IF NOT EXISTS cached_tokens INT NOT NULL DEFAULT 0,\n ADD COLUMN IF NOT EXISTS cache_creation_tokens INT NOT NULL DEFAULT 0\n `).catch(() => { /* MySQL < 8.0 may not support IF NOT EXISTS — ignore if columns already exist */ })\n }\n\n record(entry: UsageEntry): void {\n this.client\n .execute(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, reasoning_tokens, cached_tokens, cache_creation_tokens,\n cost_usd, session_id, user_id, feature, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.reasoningTokens ?? 0,\n entry.cachedTokens ?? 0,\n entry.cacheCreationTokens ?? 0,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? null,\n entry.feature ?? null,\n entry.timestamp,\n ],\n )\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MySQLStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const [rows] = await this.client.execute(\n 'SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC',\n )\n return (rows as Array<Record<string, unknown>>).map(rowToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.client.execute('DELETE FROM tokenwatch_usage')\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.client.execute(\n 'DELETE FROM tokenwatch_usage WHERE session_id = ?',\n [sessionId],\n )\n }\n}\n\nfunction rowToEntry(r: Record<string, unknown>): UsageEntry {\n const reasoningTokens = (r['reasoning_tokens'] as number | null) ?? 0\n const cachedTokens = (r['cached_tokens'] as number | null) ?? 0\n const cacheCreationTokens = (r['cache_creation_tokens'] as number | null) ?? 0\n return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\n ...(reasoningTokens > 0 && { reasoningTokens }),\n ...(cachedTokens > 0 && { cachedTokens }),\n ...(cacheCreationTokens > 0 && { cacheCreationTokens }),\n costUSD: Number(r['cost_usd']),\n ...(r['session_id'] != null && { sessionId: r['session_id'] as string }),\n ...(r['user_id'] != null && { userId: r['user_id'] as string }),\n ...(r['feature'] != null && { feature: r['feature'] as string }),\n timestamp:\n r['timestamp'] instanceof Date\n ? (r['timestamp'] as Date).toISOString()\n : (r['timestamp'] as string),\n }\n}\n","import type { IStorage, UsageEntry } from '../types/index.js'\n\n/**\n * IStorage adapter for MongoDB using the official `mongodb` driver.\n *\n * Install peer dep: npm install mongodb\n *\n * @example\n * ```ts\n * import { MongoClient } from 'mongodb'\n * import { createTracker } from '@diogonzafe/tokenwatch'\n * import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'\n *\n * const client = new MongoClient(process.env.MONGO_URL!)\n * await client.connect()\n *\n * const storage = new MongoStorage(client.db('myapp'))\n * const tracker = createTracker({ storage })\n * ```\n *\n * Recommended index (run once at startup):\n * ```ts\n * await storage.createIndexes()\n * ```\n */\n\n// Minimal structural types so the adapter compiles without `mongodb` installed\ninterface MongoDocument {\n _id?: unknown\n model: string\n inputTokens: number\n outputTokens: number\n reasoningTokens?: number\n cachedTokens?: number\n cacheCreationTokens?: number\n costUSD: number\n sessionId?: string | null\n userId?: string | null\n feature?: string | null\n timestamp: string\n}\n\ninterface MongoCursor {\n sort(sort: Record<string, unknown>): MongoCursor\n toArray(): Promise<MongoDocument[]>\n}\n\ninterface Collection {\n insertOne(doc: MongoDocument): Promise<unknown>\n find(filter: Record<string, unknown>): MongoCursor\n deleteMany(filter: Record<string, unknown>): Promise<unknown>\n createIndex(index: Record<string, unknown>): Promise<unknown>\n}\n\ninterface Database {\n collection(name: string): Collection\n}\n\nconst COLLECTION = 'tokenwatch_usage'\n\nexport class MongoStorage implements IStorage {\n private readonly col: Collection\n\n constructor(db: Database) {\n this.col = db.collection(COLLECTION)\n }\n\n /** Creates recommended indexes for query performance. Call once at startup. */\n async createIndexes(): Promise<void> {\n await this.col.createIndex({ timestamp: 1 })\n await this.col.createIndex({ sessionId: 1 })\n await this.col.createIndex({ userId: 1 })\n await this.col.createIndex({ model: 1 })\n }\n\n record(entry: UsageEntry): void {\n this.col\n .insertOne({\n model: entry.model,\n inputTokens: entry.inputTokens,\n outputTokens: entry.outputTokens,\n ...(entry.reasoningTokens !== undefined && { reasoningTokens: entry.reasoningTokens }),\n ...(entry.cachedTokens !== undefined && { cachedTokens: entry.cachedTokens }),\n ...(entry.cacheCreationTokens !== undefined && { cacheCreationTokens: entry.cacheCreationTokens }),\n costUSD: entry.costUSD,\n sessionId: entry.sessionId ?? null,\n userId: entry.userId ?? null,\n ...(entry.feature !== undefined && { feature: entry.feature }),\n timestamp: entry.timestamp,\n })\n .catch((err: unknown) => {\n console.warn('[tokenwatch] MongoStorage.record failed:', err)\n })\n }\n\n async getAll(): Promise<UsageEntry[]> {\n const docs = await this.col.find({}).sort({ timestamp: 1 }).toArray()\n return docs.map(docToEntry)\n }\n\n async clearAll(): Promise<void> {\n await this.col.deleteMany({})\n }\n\n async clearSession(sessionId: string): Promise<void> {\n await this.col.deleteMany({ sessionId })\n }\n}\n\nfunction docToEntry(doc: MongoDocument): UsageEntry {\n return {\n model: doc.model,\n inputTokens: doc.inputTokens,\n outputTokens: doc.outputTokens,\n ...(doc.reasoningTokens != null && doc.reasoningTokens > 0 && { reasoningTokens: doc.reasoningTokens }),\n ...(doc.cachedTokens != null && doc.cachedTokens > 0 && { cachedTokens: doc.cachedTokens }),\n ...(doc.cacheCreationTokens != null && doc.cacheCreationTokens > 0 && { cacheCreationTokens: doc.cacheCreationTokens }),\n costUSD: doc.costUSD,\n ...(doc.sessionId != null && { sessionId: doc.sessionId }),\n ...(doc.userId != null && { userId: doc.userId }),\n ...(doc.feature != null && { feature: doc.feature }),\n timestamp: doc.timestamp,\n }\n}\n"],"mappings":"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","names":["rowToEntry"]}
@@ -1,4 +1,4 @@
1
- import { I as IStorage, U as UsageEntry } from './index-B_EmA3K7.cjs';
1
+ import { I as IStorage, U as UsageEntry } from './index-CJKk1hHw.cjs';
2
2
 
3
3
  /**
4
4
  * IStorage adapter for PostgreSQL using the `pg` driver.
@@ -28,7 +28,7 @@ declare class PostgresStorage implements IStorage {
28
28
  private readonly client;
29
29
  constructor(client: QueryClient$1);
30
30
  /** Creates the `tokenwatch_usage` table if it does not already exist.
31
- * Also adds new columns for databases created before v0.2.0. */
31
+ * Also adds new columns for databases created before v0.2.0 / v0.3.0. */
32
32
  migrate(): Promise<void>;
33
33
  record(entry: UsageEntry): void;
34
34
  getAll(): Promise<UsageEntry[]>;
@@ -61,7 +61,7 @@ declare class MySQLStorage implements IStorage {
61
61
  private readonly client;
62
62
  constructor(client: QueryClient);
63
63
  /** Creates the `tokenwatch_usage` table if it does not already exist.
64
- * Also adds new columns for databases created before v0.2.0. */
64
+ * Also adds new columns for databases created before v0.2.0 / v0.3.0. */
65
65
  migrate(): Promise<void>;
66
66
  record(entry: UsageEntry): void;
67
67
  getAll(): Promise<UsageEntry[]>;
@@ -98,6 +98,8 @@ interface MongoDocument {
98
98
  inputTokens: number;
99
99
  outputTokens: number;
100
100
  reasoningTokens?: number;
101
+ cachedTokens?: number;
102
+ cacheCreationTokens?: number;
101
103
  costUSD: number;
102
104
  sessionId?: string | null;
103
105
  userId?: string | null;
@@ -1,4 +1,4 @@
1
- import { I as IStorage, U as UsageEntry } from './index-B_EmA3K7.js';
1
+ import { I as IStorage, U as UsageEntry } from './index-CJKk1hHw.js';
2
2
 
3
3
  /**
4
4
  * IStorage adapter for PostgreSQL using the `pg` driver.
@@ -28,7 +28,7 @@ declare class PostgresStorage implements IStorage {
28
28
  private readonly client;
29
29
  constructor(client: QueryClient$1);
30
30
  /** Creates the `tokenwatch_usage` table if it does not already exist.
31
- * Also adds new columns for databases created before v0.2.0. */
31
+ * Also adds new columns for databases created before v0.2.0 / v0.3.0. */
32
32
  migrate(): Promise<void>;
33
33
  record(entry: UsageEntry): void;
34
34
  getAll(): Promise<UsageEntry[]>;
@@ -61,7 +61,7 @@ declare class MySQLStorage implements IStorage {
61
61
  private readonly client;
62
62
  constructor(client: QueryClient);
63
63
  /** Creates the `tokenwatch_usage` table if it does not already exist.
64
- * Also adds new columns for databases created before v0.2.0. */
64
+ * Also adds new columns for databases created before v0.2.0 / v0.3.0. */
65
65
  migrate(): Promise<void>;
66
66
  record(entry: UsageEntry): void;
67
67
  getAll(): Promise<UsageEntry[]>;
@@ -98,6 +98,8 @@ interface MongoDocument {
98
98
  inputTokens: number;
99
99
  outputTokens: number;
100
100
  reasoningTokens?: number;
101
+ cachedTokens?: number;
102
+ cacheCreationTokens?: number;
101
103
  costUSD: number;
102
104
  sessionId?: string | null;
103
105
  userId?: string | null;