@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 +224 -13
- package/dist/adapters.cjs +61 -36
- package/dist/adapters.cjs.map +1 -1
- package/dist/adapters.d.cts +5 -3
- package/dist/adapters.d.ts +5 -3
- package/dist/adapters.js +61 -36
- package/dist/adapters.js.map +1 -1
- package/dist/cli.js +455 -51
- package/dist/cli.js.map +1 -1
- package/dist/{index-B_EmA3K7.d.cts → index-CJKk1hHw.d.cts} +62 -2
- package/dist/{index-B_EmA3K7.d.ts → index-CJKk1hHw.d.ts} +62 -2
- package/dist/index.cjs +548 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -3
- package/dist/index.d.ts +20 -3
- package/dist/index.js +547 -64
- package/dist/index.js.map +1 -1
- package/dist/langchain.cjs +62 -0
- package/dist/langchain.cjs.map +1 -0
- package/dist/langchain.d.cts +68 -0
- package/dist/langchain.d.ts +68 -0
- package/dist/langchain.js +35 -0
- package/dist/langchain.js.map +1 -0
- package/package.json +11 -2
- package/prices.json +167 -3
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':
|
|
173
|
-
// 'o3':
|
|
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
|
|
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,
|
|
215
|
-
"claude-sonnet-4-6": { "input": 3.00,
|
|
216
|
-
"gemini-2.5-pro": { "input": 1.25,
|
|
217
|
-
"deepseek-chat": { "input": 0.28,
|
|
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** —
|
|
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
|
|
41
|
-
model
|
|
42
|
-
input_tokens
|
|
43
|
-
output_tokens
|
|
44
|
-
reasoning_tokens
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
53
|
-
ALTER TABLE tokenwatch_usage ADD COLUMN IF NOT EXISTS reasoning_tokens INTEGER NOT NULL DEFAULT 0
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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,
|
|
65
|
-
|
|
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
|
|
124
|
-
model
|
|
125
|
-
input_tokens
|
|
126
|
-
output_tokens
|
|
127
|
-
reasoning_tokens
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
|
138
|
-
ADD COLUMN IF NOT EXISTS feature
|
|
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,
|
|
146
|
-
|
|
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 },
|
package/dist/adapters.cjs.map
CHANGED
|
@@ -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"]}
|
package/dist/adapters.d.cts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { I as IStorage, U as UsageEntry } from './index-
|
|
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;
|
package/dist/adapters.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { I as IStorage, U as UsageEntry } from './index-
|
|
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;
|