@diogonzafe/tokenwatch 0.1.6 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +29 -41
- 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 +29 -41
- package/dist/index.js.map +1 -1
- package/package.json +19 -2
|
@@ -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 };
|
package/dist/adapters.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
// src/adapters/postgres.ts
|
|
2
|
+
var PostgresStorage = class {
|
|
3
|
+
constructor(client) {
|
|
4
|
+
this.client = client;
|
|
5
|
+
}
|
|
6
|
+
client;
|
|
7
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist. */
|
|
8
|
+
async migrate() {
|
|
9
|
+
await this.client.query(`
|
|
10
|
+
CREATE TABLE IF NOT EXISTS tokenwatch_usage (
|
|
11
|
+
id BIGSERIAL PRIMARY KEY,
|
|
12
|
+
model TEXT NOT NULL,
|
|
13
|
+
input_tokens INTEGER NOT NULL,
|
|
14
|
+
output_tokens INTEGER NOT NULL,
|
|
15
|
+
cost_usd NUMERIC NOT NULL,
|
|
16
|
+
session_id TEXT,
|
|
17
|
+
user_id TEXT,
|
|
18
|
+
timestamp TIMESTAMPTZ NOT NULL
|
|
19
|
+
)
|
|
20
|
+
`);
|
|
21
|
+
}
|
|
22
|
+
record(entry) {
|
|
23
|
+
this.client.query(
|
|
24
|
+
`INSERT INTO tokenwatch_usage
|
|
25
|
+
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
26
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7)`,
|
|
27
|
+
[
|
|
28
|
+
entry.model,
|
|
29
|
+
entry.inputTokens,
|
|
30
|
+
entry.outputTokens,
|
|
31
|
+
entry.costUSD,
|
|
32
|
+
entry.sessionId ?? null,
|
|
33
|
+
entry.userId ?? null,
|
|
34
|
+
entry.timestamp
|
|
35
|
+
]
|
|
36
|
+
).catch((err) => {
|
|
37
|
+
console.warn("[tokenwatch] PostgresStorage.record failed:", err);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
async getAll() {
|
|
41
|
+
const result = await this.client.query(
|
|
42
|
+
"SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC"
|
|
43
|
+
);
|
|
44
|
+
return result.rows.map(rowToEntry);
|
|
45
|
+
}
|
|
46
|
+
async clearAll() {
|
|
47
|
+
await this.client.query("DELETE FROM tokenwatch_usage");
|
|
48
|
+
}
|
|
49
|
+
async clearSession(sessionId) {
|
|
50
|
+
await this.client.query(
|
|
51
|
+
"DELETE FROM tokenwatch_usage WHERE session_id = $1",
|
|
52
|
+
[sessionId]
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
function rowToEntry(r) {
|
|
57
|
+
return {
|
|
58
|
+
model: r["model"],
|
|
59
|
+
inputTokens: r["input_tokens"],
|
|
60
|
+
outputTokens: r["output_tokens"],
|
|
61
|
+
costUSD: Number(r["cost_usd"]),
|
|
62
|
+
...r["session_id"] != null && { sessionId: r["session_id"] },
|
|
63
|
+
...r["user_id"] != null && { userId: r["user_id"] },
|
|
64
|
+
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// src/adapters/mysql.ts
|
|
69
|
+
var MySQLStorage = class {
|
|
70
|
+
constructor(client) {
|
|
71
|
+
this.client = client;
|
|
72
|
+
}
|
|
73
|
+
client;
|
|
74
|
+
/** Creates the `tokenwatch_usage` table if it does not already exist. */
|
|
75
|
+
async migrate() {
|
|
76
|
+
await this.client.execute(`
|
|
77
|
+
CREATE TABLE IF NOT EXISTS tokenwatch_usage (
|
|
78
|
+
id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
|
|
79
|
+
model VARCHAR(255) NOT NULL,
|
|
80
|
+
input_tokens INT NOT NULL,
|
|
81
|
+
output_tokens INT NOT NULL,
|
|
82
|
+
cost_usd DECIMAL(18,8) NOT NULL,
|
|
83
|
+
session_id VARCHAR(255),
|
|
84
|
+
user_id VARCHAR(255),
|
|
85
|
+
timestamp DATETIME(3) NOT NULL
|
|
86
|
+
)
|
|
87
|
+
`);
|
|
88
|
+
}
|
|
89
|
+
record(entry) {
|
|
90
|
+
this.client.execute(
|
|
91
|
+
`INSERT INTO tokenwatch_usage
|
|
92
|
+
(model, input_tokens, output_tokens, cost_usd, session_id, user_id, timestamp)
|
|
93
|
+
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
|
94
|
+
[
|
|
95
|
+
entry.model,
|
|
96
|
+
entry.inputTokens,
|
|
97
|
+
entry.outputTokens,
|
|
98
|
+
entry.costUSD,
|
|
99
|
+
entry.sessionId ?? null,
|
|
100
|
+
entry.userId ?? null,
|
|
101
|
+
entry.timestamp
|
|
102
|
+
]
|
|
103
|
+
).catch((err) => {
|
|
104
|
+
console.warn("[tokenwatch] MySQLStorage.record failed:", err);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async getAll() {
|
|
108
|
+
const [rows] = await this.client.execute(
|
|
109
|
+
"SELECT * FROM tokenwatch_usage ORDER BY timestamp ASC"
|
|
110
|
+
);
|
|
111
|
+
return rows.map(rowToEntry2);
|
|
112
|
+
}
|
|
113
|
+
async clearAll() {
|
|
114
|
+
await this.client.execute("DELETE FROM tokenwatch_usage");
|
|
115
|
+
}
|
|
116
|
+
async clearSession(sessionId) {
|
|
117
|
+
await this.client.execute(
|
|
118
|
+
"DELETE FROM tokenwatch_usage WHERE session_id = ?",
|
|
119
|
+
[sessionId]
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
function rowToEntry2(r) {
|
|
124
|
+
return {
|
|
125
|
+
model: r["model"],
|
|
126
|
+
inputTokens: r["input_tokens"],
|
|
127
|
+
outputTokens: r["output_tokens"],
|
|
128
|
+
costUSD: Number(r["cost_usd"]),
|
|
129
|
+
...r["session_id"] != null && { sessionId: r["session_id"] },
|
|
130
|
+
...r["user_id"] != null && { userId: r["user_id"] },
|
|
131
|
+
timestamp: r["timestamp"] instanceof Date ? r["timestamp"].toISOString() : r["timestamp"]
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/adapters/mongodb.ts
|
|
136
|
+
var COLLECTION = "tokenwatch_usage";
|
|
137
|
+
var MongoStorage = class {
|
|
138
|
+
col;
|
|
139
|
+
constructor(db) {
|
|
140
|
+
this.col = db.collection(COLLECTION);
|
|
141
|
+
}
|
|
142
|
+
/** Creates recommended indexes for query performance. Call once at startup. */
|
|
143
|
+
async createIndexes() {
|
|
144
|
+
await this.col.createIndex({ timestamp: 1 });
|
|
145
|
+
await this.col.createIndex({ sessionId: 1 });
|
|
146
|
+
await this.col.createIndex({ userId: 1 });
|
|
147
|
+
await this.col.createIndex({ model: 1 });
|
|
148
|
+
}
|
|
149
|
+
record(entry) {
|
|
150
|
+
this.col.insertOne({
|
|
151
|
+
model: entry.model,
|
|
152
|
+
inputTokens: entry.inputTokens,
|
|
153
|
+
outputTokens: entry.outputTokens,
|
|
154
|
+
costUSD: entry.costUSD,
|
|
155
|
+
sessionId: entry.sessionId ?? null,
|
|
156
|
+
userId: entry.userId ?? null,
|
|
157
|
+
timestamp: entry.timestamp
|
|
158
|
+
}).catch((err) => {
|
|
159
|
+
console.warn("[tokenwatch] MongoStorage.record failed:", err);
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
async getAll() {
|
|
163
|
+
const docs = await this.col.find({}).toArray();
|
|
164
|
+
return docs.map(docToEntry);
|
|
165
|
+
}
|
|
166
|
+
async clearAll() {
|
|
167
|
+
await this.col.deleteMany({});
|
|
168
|
+
}
|
|
169
|
+
async clearSession(sessionId) {
|
|
170
|
+
await this.col.deleteMany({ sessionId });
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
function docToEntry(doc) {
|
|
174
|
+
return {
|
|
175
|
+
model: doc.model,
|
|
176
|
+
inputTokens: doc.inputTokens,
|
|
177
|
+
outputTokens: doc.outputTokens,
|
|
178
|
+
costUSD: doc.costUSD,
|
|
179
|
+
...doc.sessionId != null && { sessionId: doc.sessionId },
|
|
180
|
+
...doc.userId != null && { userId: doc.userId },
|
|
181
|
+
timestamp: doc.timestamp
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
export {
|
|
185
|
+
MongoStorage,
|
|
186
|
+
MySQLStorage,
|
|
187
|
+
PostgresStorage
|
|
188
|
+
};
|
|
189
|
+
//# sourceMappingURL=adapters.js.map
|