@diogonzafe/tokenwatch 0.1.7 → 0.1.9
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 +107 -11
- package/dist/adapters.cjs +218 -0
- package/dist/adapters.cjs.map +1 -0
- package/dist/adapters.d.cts +125 -0
- package/dist/adapters.d.ts +125 -0
- package/dist/adapters.js +189 -0
- package/dist/adapters.js.map +1 -0
- package/dist/index-Cy_sl3FI.d.cts +89 -0
- package/dist/index-Cy_sl3FI.d.ts +89 -0
- package/dist/index.cjs +25 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -87
- package/dist/index.d.ts +3 -87
- package/dist/index.js +25 -17
- package/dist/index.js.map +1 -1
- package/package.json +19 -2
package/README.md
CHANGED
|
@@ -17,6 +17,11 @@ npm install openai # OpenAI / DeepSeek
|
|
|
17
17
|
npm install @anthropic-ai/sdk # Anthropic
|
|
18
18
|
npm install @google/generative-ai # Gemini
|
|
19
19
|
npm install better-sqlite3 # optional — only for storage: 'sqlite'
|
|
20
|
+
|
|
21
|
+
# Database adapters (optional — only if using @diogonzafe/tokenwatch/adapters)
|
|
22
|
+
npm install pg # PostgreSQL
|
|
23
|
+
npm install mysql2 # MySQL / MariaDB
|
|
24
|
+
npm install mongodb # MongoDB
|
|
20
25
|
```
|
|
21
26
|
|
|
22
27
|
---
|
|
@@ -28,7 +33,7 @@ import { createTracker } from '@diogonzafe/tokenwatch'
|
|
|
28
33
|
|
|
29
34
|
const tracker = createTracker({
|
|
30
35
|
// All fields are optional
|
|
31
|
-
storage: 'memory', // 'memory' (default) | 'sqlite'
|
|
36
|
+
storage: 'memory', // 'memory' (default) | 'sqlite' | IStorage instance
|
|
32
37
|
alertThreshold: 1.00, // USD — fires webhookUrl when exceeded
|
|
33
38
|
webhookUrl: 'https://...', // Discord / Slack webhook
|
|
34
39
|
syncPrices: true, // fetch fresh prices from GitHub (default: true)
|
|
@@ -135,8 +140,10 @@ const res = await deepseek.chat.completions.create({
|
|
|
135
140
|
|
|
136
141
|
## Reports
|
|
137
142
|
|
|
143
|
+
All report methods are async:
|
|
144
|
+
|
|
138
145
|
```ts
|
|
139
|
-
tracker.getReport()
|
|
146
|
+
const report = await tracker.getReport()
|
|
140
147
|
// {
|
|
141
148
|
// totalCostUSD: 0.087,
|
|
142
149
|
// totalTokens: { input: 24000, output: 6000 },
|
|
@@ -151,12 +158,12 @@ tracker.getReport()
|
|
|
151
158
|
|
|
152
159
|
tracker.getModelInfo('gpt-4o')
|
|
153
160
|
// { input: 2.5, output: 10, maxInputTokens: 128000 }
|
|
154
|
-
// Returns null if the model is unknown
|
|
161
|
+
// Returns null if the model is unknown (synchronous)
|
|
155
162
|
|
|
156
|
-
tracker.reset() // clear all data
|
|
157
|
-
tracker.resetSession('session_abc') // clear one session
|
|
158
|
-
tracker.exportJSON() // full report as JSON string
|
|
159
|
-
tracker.exportCSV() // all calls as CSV string
|
|
163
|
+
await tracker.reset() // clear all data
|
|
164
|
+
await tracker.resetSession('session_abc') // clear one session
|
|
165
|
+
await tracker.exportJSON() // full report as JSON string
|
|
166
|
+
await tracker.exportCSV() // all calls as CSV string
|
|
160
167
|
```
|
|
161
168
|
|
|
162
169
|
---
|
|
@@ -167,7 +174,7 @@ Prices are resolved in this priority order:
|
|
|
167
174
|
|
|
168
175
|
1. **`customPrices`** — your own overrides, highest priority
|
|
169
176
|
2. **Remote `prices.json`** — fetched from GitHub, cached for 24h in `~/.tokenwatch/prices.json`
|
|
170
|
-
3. **Bundled `prices.json`** — always-present fallback, updated
|
|
177
|
+
3. **Bundled `prices.json`** — always-present fallback, updated daily via GitHub Action
|
|
171
178
|
|
|
172
179
|
If a model is not found in any layer, cost is recorded as **$0** with a `console.warn`.
|
|
173
180
|
|
|
@@ -188,11 +195,20 @@ Prices are in **USD per 1 million tokens**.
|
|
|
188
195
|
}
|
|
189
196
|
```
|
|
190
197
|
|
|
191
|
-
Prices are updated every
|
|
198
|
+
Prices are updated every day via a GitHub Action that pulls from the [LiteLLM community model registry](https://github.com/BerriAI/litellm). New models are auto-discovered — no manual updates needed.
|
|
192
199
|
|
|
193
200
|
---
|
|
194
201
|
|
|
195
|
-
##
|
|
202
|
+
## Storage
|
|
203
|
+
|
|
204
|
+
### In-memory (default)
|
|
205
|
+
|
|
206
|
+
```ts
|
|
207
|
+
const tracker = createTracker({ storage: 'memory' })
|
|
208
|
+
// Resets on process restart. Good for short-lived processes and testing.
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### SQLite
|
|
196
212
|
|
|
197
213
|
For persistent tracking across restarts:
|
|
198
214
|
|
|
@@ -205,6 +221,76 @@ const tracker = createTracker({ storage: 'sqlite' })
|
|
|
205
221
|
// Data stored in ~/.tokenwatch/usage.db
|
|
206
222
|
```
|
|
207
223
|
|
|
224
|
+
### PostgreSQL
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
npm install pg
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
```ts
|
|
231
|
+
import { Pool } from 'pg'
|
|
232
|
+
import { createTracker } from '@diogonzafe/tokenwatch'
|
|
233
|
+
import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
234
|
+
|
|
235
|
+
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
236
|
+
const storage = new PostgresStorage(pool)
|
|
237
|
+
await storage.migrate() // creates tokenwatch_usage table if it doesn't exist
|
|
238
|
+
|
|
239
|
+
const tracker = createTracker({ storage })
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### MySQL / MariaDB
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
npm install mysql2
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```ts
|
|
249
|
+
import mysql from 'mysql2/promise'
|
|
250
|
+
import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
251
|
+
|
|
252
|
+
const pool = mysql.createPool({ uri: process.env.MYSQL_URL })
|
|
253
|
+
const storage = new MySQLStorage(pool)
|
|
254
|
+
await storage.migrate()
|
|
255
|
+
|
|
256
|
+
const tracker = createTracker({ storage })
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### MongoDB
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm install mongodb
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { MongoClient } from 'mongodb'
|
|
267
|
+
import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
268
|
+
|
|
269
|
+
const client = new MongoClient(process.env.MONGO_URL!)
|
|
270
|
+
await client.connect()
|
|
271
|
+
const storage = new MongoStorage(client.db('myapp'))
|
|
272
|
+
await storage.createIndexes() // optional but recommended
|
|
273
|
+
|
|
274
|
+
const tracker = createTracker({ storage })
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Custom adapter
|
|
278
|
+
|
|
279
|
+
Any object that implements `IStorage` works:
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
import type { IStorage, UsageEntry } from '@diogonzafe/tokenwatch'
|
|
283
|
+
|
|
284
|
+
class RedisStorage implements IStorage {
|
|
285
|
+
record(entry: UsageEntry): void { /* ... */ }
|
|
286
|
+
async getAll(): Promise<UsageEntry[]> { /* ... */ }
|
|
287
|
+
async clearAll(): Promise<void> { /* ... */ }
|
|
288
|
+
async clearSession(sessionId: string): Promise<void> { /* ... */ }
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const tracker = createTracker({ storage: new RedisStorage() })
|
|
292
|
+
```
|
|
293
|
+
|
|
208
294
|
---
|
|
209
295
|
|
|
210
296
|
## Alerts & Webhooks
|
|
@@ -235,13 +321,23 @@ npx tokenwatch help # show help
|
|
|
235
321
|
|
|
236
322
|
---
|
|
237
323
|
|
|
324
|
+
## Privacy & Security
|
|
325
|
+
|
|
326
|
+
- Prompt and response **content is never read or stored** — only token counts and model names
|
|
327
|
+
- API keys are **never accessed** by tokenwatch — they remain solely in the provider client
|
|
328
|
+
- SQLite, Postgres, MySQL, and MongoDB data stays **entirely in your own infrastructure** — nothing is transmitted to external services
|
|
329
|
+
- The wrapper is a thin `Proxy` with **no outbound network calls** of its own (only the daily price sync script fetches external data)
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
238
333
|
## Behaviour Guarantees
|
|
239
334
|
|
|
240
335
|
- `__sessionId` and `__userId` are **stripped before** the request reaches the API
|
|
241
336
|
- The response object returned is **identical** to the original SDK response
|
|
242
|
-
-
|
|
337
|
+
- `track()` is **synchronous and non-blocking** — zero latency added to API calls
|
|
243
338
|
- If the API call **fails**, no cost is recorded and the original error is re-thrown unchanged
|
|
244
339
|
- Streaming is fully supported — usage is accumulated from the final stream event
|
|
340
|
+
- Database writes from `record()` are **fire-and-forget** — a storage failure never interrupts your API call
|
|
245
341
|
|
|
246
342
|
---
|
|
247
343
|
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/adapters/index.ts
|
|
21
|
+
var adapters_exports = {};
|
|
22
|
+
__export(adapters_exports, {
|
|
23
|
+
MongoStorage: () => MongoStorage,
|
|
24
|
+
MySQLStorage: () => MySQLStorage,
|
|
25
|
+
PostgresStorage: () => PostgresStorage
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(adapters_exports);
|
|
28
|
+
|
|
29
|
+
// src/adapters/postgres.ts
|
|
30
|
+
var PostgresStorage = class {
|
|
31
|
+
constructor(client) {
|
|
32
|
+
this.client = client;
|
|
33
|
+
}
|
|
34
|
+
client;
|
|
35
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist. */
|
|
36
|
+
async migrate() {
|
|
37
|
+
await this.client.query(`
|
|
38
|
+
CREATE TABLE IF NOT EXISTS tokenwatch_usage (
|
|
39
|
+
id BIGSERIAL PRIMARY KEY,
|
|
40
|
+
model TEXT NOT NULL,
|
|
41
|
+
input_tokens INTEGER NOT NULL,
|
|
42
|
+
output_tokens INTEGER NOT NULL,
|
|
43
|
+
cost_usd NUMERIC NOT NULL,
|
|
44
|
+
session_id TEXT,
|
|
45
|
+
user_id TEXT,
|
|
46
|
+
timestamp TIMESTAMPTZ NOT NULL
|
|
47
|
+
)
|
|
48
|
+
`);
|
|
49
|
+
}
|
|
50
|
+
record(entry) {
|
|
51
|
+
this.client.query(
|
|
52
|
+
`INSERT INTO tokenwatch_usage
|
|
53
|
+
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
54
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
55
|
+
[
|
|
56
|
+
entry.model,
|
|
57
|
+
entry.inputTokens,
|
|
58
|
+
entry.outputTokens,
|
|
59
|
+
entry.costUSD,
|
|
60
|
+
entry.sessionId ?? null,
|
|
61
|
+
entry.userId ?? null,
|
|
62
|
+
entry.timestamp
|
|
63
|
+
]
|
|
64
|
+
).catch((err) => {
|
|
65
|
+
console.warn("[tokenwatch] PostgresStorage.record failed:", err);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
async getAll() {
|
|
69
|
+
const result = await this.client.query(
|
|
70
|
+
"SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC"
|
|
71
|
+
);
|
|
72
|
+
return result.rows.map(rowToEntry);
|
|
73
|
+
}
|
|
74
|
+
async clearAll() {
|
|
75
|
+
await this.client.query("DELETE FROM tokenwatch_usage");
|
|
76
|
+
}
|
|
77
|
+
async clearSession(sessionId) {
|
|
78
|
+
await this.client.query(
|
|
79
|
+
"DELETE FROM tokenwatch_usage WHERE session_id = $1",
|
|
80
|
+
[sessionId]
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
function rowToEntry(r) {
|
|
85
|
+
return {
|
|
86
|
+
model: r["model"],
|
|
87
|
+
inputTokens: r["input_tokens"],
|
|
88
|
+
outputTokens: r["output_tokens"],
|
|
89
|
+
costUSD: Number(r["cost_usd"]),
|
|
90
|
+
...r["session_id"] != null && { sessionId: r["session_id"] },
|
|
91
|
+
...r["user_id"] != null && { userId: r["user_id"] },
|
|
92
|
+
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/adapters/mysql.ts
|
|
97
|
+
var MySQLStorage = class {
|
|
98
|
+
constructor(client) {
|
|
99
|
+
this.client = client;
|
|
100
|
+
}
|
|
101
|
+
client;
|
|
102
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist. */
|
|
103
|
+
async migrate() {
|
|
104
|
+
await this.client.execute(`
|
|
105
|
+
CREATE TABLE IF NOT EXISTS tokenwatch_usage (
|
|
106
|
+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
107
|
+
model VARCHAR(255) NOT NULL,
|
|
108
|
+
input_tokens INT NOT NULL,
|
|
109
|
+
output_tokens INT NOT NULL,
|
|
110
|
+
cost_usd DECIMAL(18,8) NOT NULL,
|
|
111
|
+
session_id VARCHAR(255),
|
|
112
|
+
user_id VARCHAR(255),
|
|
113
|
+
timestamp DATETIME(3) NOT NULL
|
|
114
|
+
)
|
|
115
|
+
`);
|
|
116
|
+
}
|
|
117
|
+
record(entry) {
|
|
118
|
+
this.client.execute(
|
|
119
|
+
`INSERT INTO tokenwatch_usage
|
|
120
|
+
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
121
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
122
|
+
[
|
|
123
|
+
entry.model,
|
|
124
|
+
entry.inputTokens,
|
|
125
|
+
entry.outputTokens,
|
|
126
|
+
entry.costUSD,
|
|
127
|
+
entry.sessionId ?? null,
|
|
128
|
+
entry.userId ?? null,
|
|
129
|
+
entry.timestamp
|
|
130
|
+
]
|
|
131
|
+
).catch((err) => {
|
|
132
|
+
console.warn("[tokenwatch] MySQLStorage.record failed:", err);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async getAll() {
|
|
136
|
+
const [rows] = await this.client.execute(
|
|
137
|
+
"SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC"
|
|
138
|
+
);
|
|
139
|
+
return rows.map(rowToEntry2);
|
|
140
|
+
}
|
|
141
|
+
async clearAll() {
|
|
142
|
+
await this.client.execute("DELETE FROM tokenwatch_usage");
|
|
143
|
+
}
|
|
144
|
+
async clearSession(sessionId) {
|
|
145
|
+
await this.client.execute(
|
|
146
|
+
"DELETE FROM tokenwatch_usage WHERE session_id = ?",
|
|
147
|
+
[sessionId]
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
function rowToEntry2(r) {
|
|
152
|
+
return {
|
|
153
|
+
model: r["model"],
|
|
154
|
+
inputTokens: r["input_tokens"],
|
|
155
|
+
outputTokens: r["output_tokens"],
|
|
156
|
+
costUSD: Number(r["cost_usd"]),
|
|
157
|
+
...r["session_id"] != null && { sessionId: r["session_id"] },
|
|
158
|
+
...r["user_id"] != null && { userId: r["user_id"] },
|
|
159
|
+
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// src/adapters/mongodb.ts
|
|
164
|
+
var COLLECTION = "tokenwatch_usage";
|
|
165
|
+
var MongoStorage = class {
|
|
166
|
+
col;
|
|
167
|
+
constructor(db) {
|
|
168
|
+
this.col = db.collection(COLLECTION);
|
|
169
|
+
}
|
|
170
|
+
/** Creates recommended indexes for query performance. Call once at startup. */
|
|
171
|
+
async createIndexes() {
|
|
172
|
+
await this.col.createIndex({ timestamp: 1 });
|
|
173
|
+
await this.col.createIndex({ sessionId: 1 });
|
|
174
|
+
await this.col.createIndex({ userId: 1 });
|
|
175
|
+
await this.col.createIndex({ model: 1 });
|
|
176
|
+
}
|
|
177
|
+
record(entry) {
|
|
178
|
+
this.col.insertOne({
|
|
179
|
+
model: entry.model,
|
|
180
|
+
inputTokens: entry.inputTokens,
|
|
181
|
+
outputTokens: entry.outputTokens,
|
|
182
|
+
costUSD: entry.costUSD,
|
|
183
|
+
sessionId: entry.sessionId ?? null,
|
|
184
|
+
userId: entry.userId ?? null,
|
|
185
|
+
timestamp: entry.timestamp
|
|
186
|
+
}).catch((err) => {
|
|
187
|
+
console.warn("[tokenwatch] MongoStorage.record failed:", err);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
async getAll() {
|
|
191
|
+
const docs = await this.col.find({}).toArray();
|
|
192
|
+
return docs.map(docToEntry);
|
|
193
|
+
}
|
|
194
|
+
async clearAll() {
|
|
195
|
+
await this.col.deleteMany({});
|
|
196
|
+
}
|
|
197
|
+
async clearSession(sessionId) {
|
|
198
|
+
await this.col.deleteMany({ sessionId });
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
function docToEntry(doc) {
|
|
202
|
+
return {
|
|
203
|
+
model: doc.model,
|
|
204
|
+
inputTokens: doc.inputTokens,
|
|
205
|
+
outputTokens: doc.outputTokens,
|
|
206
|
+
costUSD: doc.costUSD,
|
|
207
|
+
...doc.sessionId != null && { sessionId: doc.sessionId },
|
|
208
|
+
...doc.userId != null && { userId: doc.userId },
|
|
209
|
+
timestamp: doc.timestamp
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
213
|
+
0 && (module.exports = {
|
|
214
|
+
MongoStorage,
|
|
215
|
+
MySQLStorage,
|
|
216
|
+
PostgresStorage
|
|
217
|
+
});
|
|
218
|
+
//# sourceMappingURL=adapters.cjs.map
|
|
@@ -0,0 +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 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 cost_usd NUMERIC NOT NULL,\n session_id TEXT,\n user_id TEXT,\n timestamp TIMESTAMPTZ NOT NULL\n )\n `)\n }\n\n record(entry: UsageEntry): void {\n this.client\n .query(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)\n VALUES ($1, $2, $3, $4, $5, $6, $7)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? 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 return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\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 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 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 cost_usd DECIMAL(18,8) NOT NULL,\n session_id VARCHAR(255),\n user_id VARCHAR(255),\n timestamp DATETIME(3) NOT NULL\n )\n `)\n }\n\n record(entry: UsageEntry): void {\n this.client\n .execute(\n `INSERT INTO tokenwatch_usage\n (model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [\n entry.model,\n entry.inputTokens,\n entry.outputTokens,\n entry.costUSD,\n entry.sessionId ?? null,\n entry.userId ?? 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 return {\n model: r['model'] as string,\n inputTokens: r['input_tokens'] as number,\n outputTokens: r['output_tokens'] as number,\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 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 costUSD: number\n sessionId?: string | null\n userId?: string | null\n timestamp: string\n}\n\ninterface Collection {\n insertOne(doc: MongoDocument): Promise<unknown>\n find(filter: Record<string, unknown>): { toArray(): Promise<MongoDocument[]> }\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 costUSD: entry.costUSD,\n sessionId: entry.sessionId ?? null,\n userId: entry.userId ?? null,\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({}).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 costUSD: doc.costUSD,\n ...(doc.sessionId != null && { sessionId: doc.sessionId }),\n ...(doc.userId != null && { userId: doc.userId }),\n timestamp: doc.timestamp,\n }\n}\n"],"mappings":"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","names":["rowToEntry"]}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { I as IStorage, U as UsageEntry } from './index-Cy_sl3FI.cjs';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IStorage adapter for PostgreSQL using the `pg` driver.
|
|
5
|
+
*
|
|
6
|
+
* Install peer dep: npm install pg
|
|
7
|
+
* Types (optional): npm install -D @types/pg
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Pool } from 'pg'
|
|
12
|
+
* import { createTracker } from '@diogonzafe/tokenwatch'
|
|
13
|
+
* import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
14
|
+
*
|
|
15
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
16
|
+
* const storage = new PostgresStorage(pool)
|
|
17
|
+
* await storage.migrate() // create table if it doesn't exist
|
|
18
|
+
*
|
|
19
|
+
* const tracker = createTracker({ storage })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface QueryClient$1 {
|
|
23
|
+
query(sql: string, values?: unknown[]): Promise<{
|
|
24
|
+
rows: unknown[];
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
declare class PostgresStorage implements IStorage {
|
|
28
|
+
private readonly client;
|
|
29
|
+
constructor(client: QueryClient$1);
|
|
30
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist. */
|
|
31
|
+
migrate(): Promise<void>;
|
|
32
|
+
record(entry: UsageEntry): void;
|
|
33
|
+
getAll(): Promise<UsageEntry[]>;
|
|
34
|
+
clearAll(): Promise<void>;
|
|
35
|
+
clearSession(sessionId: string): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* IStorage adapter for MySQL / MariaDB using the `mysql2` driver.
|
|
40
|
+
*
|
|
41
|
+
* Install peer dep: npm install mysql2
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import mysql from 'mysql2/promise'
|
|
46
|
+
* import { createTracker } from '@diogonzafe/tokenwatch'
|
|
47
|
+
* import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
48
|
+
*
|
|
49
|
+
* const pool = mysql.createPool({ uri: process.env.MYSQL_URL })
|
|
50
|
+
* const storage = new MySQLStorage(pool)
|
|
51
|
+
* await storage.migrate() // create table if it doesn't exist
|
|
52
|
+
*
|
|
53
|
+
* const tracker = createTracker({ storage })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
interface QueryClient {
|
|
57
|
+
execute(sql: string, values?: unknown[]): Promise<[unknown]>;
|
|
58
|
+
}
|
|
59
|
+
declare class MySQLStorage implements IStorage {
|
|
60
|
+
private readonly client;
|
|
61
|
+
constructor(client: QueryClient);
|
|
62
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist. */
|
|
63
|
+
migrate(): Promise<void>;
|
|
64
|
+
record(entry: UsageEntry): void;
|
|
65
|
+
getAll(): Promise<UsageEntry[]>;
|
|
66
|
+
clearAll(): Promise<void>;
|
|
67
|
+
clearSession(sessionId: string): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* IStorage adapter for MongoDB using the official `mongodb` driver.
|
|
72
|
+
*
|
|
73
|
+
* Install peer dep: npm install mongodb
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { MongoClient } from 'mongodb'
|
|
78
|
+
* import { createTracker } from '@diogonzafe/tokenwatch'
|
|
79
|
+
* import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
80
|
+
*
|
|
81
|
+
* const client = new MongoClient(process.env.MONGO_URL!)
|
|
82
|
+
* await client.connect()
|
|
83
|
+
*
|
|
84
|
+
* const storage = new MongoStorage(client.db('myapp'))
|
|
85
|
+
* const tracker = createTracker({ storage })
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* Recommended index (run once at startup):
|
|
89
|
+
* ```ts
|
|
90
|
+
* await storage.createIndexes()
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
interface MongoDocument {
|
|
94
|
+
_id?: unknown;
|
|
95
|
+
model: string;
|
|
96
|
+
inputTokens: number;
|
|
97
|
+
outputTokens: number;
|
|
98
|
+
costUSD: number;
|
|
99
|
+
sessionId?: string | null;
|
|
100
|
+
userId?: string | null;
|
|
101
|
+
timestamp: string;
|
|
102
|
+
}
|
|
103
|
+
interface Collection {
|
|
104
|
+
insertOne(doc: MongoDocument): Promise<unknown>;
|
|
105
|
+
find(filter: Record<string, unknown>): {
|
|
106
|
+
toArray(): Promise<MongoDocument[]>;
|
|
107
|
+
};
|
|
108
|
+
deleteMany(filter: Record<string, unknown>): Promise<unknown>;
|
|
109
|
+
createIndex(index: Record<string, unknown>): Promise<unknown>;
|
|
110
|
+
}
|
|
111
|
+
interface Database {
|
|
112
|
+
collection(name: string): Collection;
|
|
113
|
+
}
|
|
114
|
+
declare class MongoStorage implements IStorage {
|
|
115
|
+
private readonly col;
|
|
116
|
+
constructor(db: Database);
|
|
117
|
+
/** Creates recommended indexes for query performance. Call once at startup. */
|
|
118
|
+
createIndexes(): Promise<void>;
|
|
119
|
+
record(entry: UsageEntry): void;
|
|
120
|
+
getAll(): Promise<UsageEntry[]>;
|
|
121
|
+
clearAll(): Promise<void>;
|
|
122
|
+
clearSession(sessionId: string): Promise<void>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { MongoStorage, MySQLStorage, PostgresStorage };
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { I as IStorage, U as UsageEntry } from './index-Cy_sl3FI.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IStorage adapter for PostgreSQL using the `pg` driver.
|
|
5
|
+
*
|
|
6
|
+
* Install peer dep: npm install pg
|
|
7
|
+
* Types (optional): npm install -D @types/pg
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import { Pool } from 'pg'
|
|
12
|
+
* import { createTracker } from '@diogonzafe/tokenwatch'
|
|
13
|
+
* import { PostgresStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
14
|
+
*
|
|
15
|
+
* const pool = new Pool({ connectionString: process.env.DATABASE_URL })
|
|
16
|
+
* const storage = new PostgresStorage(pool)
|
|
17
|
+
* await storage.migrate() // create table if it doesn't exist
|
|
18
|
+
*
|
|
19
|
+
* const tracker = createTracker({ storage })
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface QueryClient$1 {
|
|
23
|
+
query(sql: string, values?: unknown[]): Promise<{
|
|
24
|
+
rows: unknown[];
|
|
25
|
+
}>;
|
|
26
|
+
}
|
|
27
|
+
declare class PostgresStorage implements IStorage {
|
|
28
|
+
private readonly client;
|
|
29
|
+
constructor(client: QueryClient$1);
|
|
30
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist. */
|
|
31
|
+
migrate(): Promise<void>;
|
|
32
|
+
record(entry: UsageEntry): void;
|
|
33
|
+
getAll(): Promise<UsageEntry[]>;
|
|
34
|
+
clearAll(): Promise<void>;
|
|
35
|
+
clearSession(sessionId: string): Promise<void>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* IStorage adapter for MySQL / MariaDB using the `mysql2` driver.
|
|
40
|
+
*
|
|
41
|
+
* Install peer dep: npm install mysql2
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import mysql from 'mysql2/promise'
|
|
46
|
+
* import { createTracker } from '@diogonzafe/tokenwatch'
|
|
47
|
+
* import { MySQLStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
48
|
+
*
|
|
49
|
+
* const pool = mysql.createPool({ uri: process.env.MYSQL_URL })
|
|
50
|
+
* const storage = new MySQLStorage(pool)
|
|
51
|
+
* await storage.migrate() // create table if it doesn't exist
|
|
52
|
+
*
|
|
53
|
+
* const tracker = createTracker({ storage })
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
interface QueryClient {
|
|
57
|
+
execute(sql: string, values?: unknown[]): Promise<[unknown]>;
|
|
58
|
+
}
|
|
59
|
+
declare class MySQLStorage implements IStorage {
|
|
60
|
+
private readonly client;
|
|
61
|
+
constructor(client: QueryClient);
|
|
62
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist. */
|
|
63
|
+
migrate(): Promise<void>;
|
|
64
|
+
record(entry: UsageEntry): void;
|
|
65
|
+
getAll(): Promise<UsageEntry[]>;
|
|
66
|
+
clearAll(): Promise<void>;
|
|
67
|
+
clearSession(sessionId: string): Promise<void>;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* IStorage adapter for MongoDB using the official `mongodb` driver.
|
|
72
|
+
*
|
|
73
|
+
* Install peer dep: npm install mongodb
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { MongoClient } from 'mongodb'
|
|
78
|
+
* import { createTracker } from '@diogonzafe/tokenwatch'
|
|
79
|
+
* import { MongoStorage } from '@diogonzafe/tokenwatch/adapters'
|
|
80
|
+
*
|
|
81
|
+
* const client = new MongoClient(process.env.MONGO_URL!)
|
|
82
|
+
* await client.connect()
|
|
83
|
+
*
|
|
84
|
+
* const storage = new MongoStorage(client.db('myapp'))
|
|
85
|
+
* const tracker = createTracker({ storage })
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* Recommended index (run once at startup):
|
|
89
|
+
* ```ts
|
|
90
|
+
* await storage.createIndexes()
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
interface MongoDocument {
|
|
94
|
+
_id?: unknown;
|
|
95
|
+
model: string;
|
|
96
|
+
inputTokens: number;
|
|
97
|
+
outputTokens: number;
|
|
98
|
+
costUSD: number;
|
|
99
|
+
sessionId?: string | null;
|
|
100
|
+
userId?: string | null;
|
|
101
|
+
timestamp: string;
|
|
102
|
+
}
|
|
103
|
+
interface Collection {
|
|
104
|
+
insertOne(doc: MongoDocument): Promise<unknown>;
|
|
105
|
+
find(filter: Record<string, unknown>): {
|
|
106
|
+
toArray(): Promise<MongoDocument[]>;
|
|
107
|
+
};
|
|
108
|
+
deleteMany(filter: Record<string, unknown>): Promise<unknown>;
|
|
109
|
+
createIndex(index: Record<string, unknown>): Promise<unknown>;
|
|
110
|
+
}
|
|
111
|
+
interface Database {
|
|
112
|
+
collection(name: string): Collection;
|
|
113
|
+
}
|
|
114
|
+
declare class MongoStorage implements IStorage {
|
|
115
|
+
private readonly col;
|
|
116
|
+
constructor(db: Database);
|
|
117
|
+
/** Creates recommended indexes for query performance. Call once at startup. */
|
|
118
|
+
createIndexes(): Promise<void>;
|
|
119
|
+
record(entry: UsageEntry): void;
|
|
120
|
+
getAll(): Promise<UsageEntry[]>;
|
|
121
|
+
clearAll(): Promise<void>;
|
|
122
|
+
clearSession(sessionId: string): Promise<void>;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export { MongoStorage, MySQLStorage, PostgresStorage };
|