ai-cost-controls 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ray Ockenfels
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,222 @@
1
+ # ai-cost-controls
2
+
3
+ Framework-agnostic AI cost controls: per-user rate limiting, token budget tracking, and response caching with pluggable cache backends.
4
+
5
+ **Zero runtime dependencies.** Bring your own cache backend (ioredis, @upstash/redis, Cloudflare KV, etc.) or use the built-in in-memory backend.
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ npm install ai-cost-controls
11
+ ```
12
+
13
+ ## Quick Start
14
+
15
+ ```typescript
16
+ import { CostControls } from 'ai-cost-controls';
17
+
18
+ const controls = new CostControls({
19
+ config: {
20
+ rateLimitPerMinute: 20,
21
+ dailyTokenBudget: 100_000,
22
+ },
23
+ });
24
+
25
+ // Check rate limit before making an AI call
26
+ if (!(await controls.checkRateLimit(userId))) {
27
+ throw new Error('Rate limited');
28
+ }
29
+
30
+ // Check cache first
31
+ const cached = await controls.getCachedResponse(userId, userMessage);
32
+ if (cached) return cached;
33
+
34
+ // After getting AI response, cache it and track tokens
35
+ await controls.cacheResponse(userId, userMessage, aiResponse);
36
+ await controls.trackTokenUsage(userId, inputTokens, outputTokens);
37
+ ```
38
+
39
+ ## Configuration
40
+
41
+ | Option | Default | Description |
42
+ |--------|---------|-------------|
43
+ | `rateLimitPerMinute` | `20` | Max requests per user per minute |
44
+ | `cacheTtlSeconds` | `300` | Response cache TTL (5 minutes) |
45
+ | `dailyTokenBudget` | `100,000` | Max tokens per user per day |
46
+ | `monthlyTokenBudget` | `2,000,000` | Max tokens per user per month |
47
+
48
+ ### Static Config
49
+
50
+ ```typescript
51
+ const controls = new CostControls({
52
+ config: {
53
+ rateLimitPerMinute: 10,
54
+ dailyTokenBudget: 50_000,
55
+ },
56
+ });
57
+ ```
58
+
59
+ ### Dynamic Config (e.g., from database)
60
+
61
+ ```typescript
62
+ const controls = new CostControls({
63
+ configLoader: async () => {
64
+ const row = await db.query('SELECT * FROM ai_config LIMIT 1');
65
+ return {
66
+ rateLimitPerMinute: row.rate_limit,
67
+ dailyTokenBudget: row.daily_budget,
68
+ };
69
+ },
70
+ });
71
+ ```
72
+
73
+ ## Cache Backends
74
+
75
+ The package ships with `InMemoryCacheBackend` (single-process only). For production multi-process or serverless deployments, implement the `CacheBackend` interface with your preferred client.
76
+
77
+ ### Interface
78
+
79
+ ```typescript
80
+ interface CacheBackend {
81
+ get(key: string): Promise<string | null>;
82
+ set(key: string, value: string, ttlMs: number): Promise<void>;
83
+ }
84
+ ```
85
+
86
+ ### ioredis
87
+
88
+ ```typescript
89
+ import Redis from 'ioredis';
90
+ import { CostControls, CacheBackend } from 'ai-cost-controls';
91
+
92
+ const redis = new Redis();
93
+
94
+ const redisBackend: CacheBackend = {
95
+ async get(key) {
96
+ return redis.get(key);
97
+ },
98
+ async set(key, value, ttlMs) {
99
+ await redis.set(key, value, 'PX', ttlMs);
100
+ },
101
+ };
102
+
103
+ const controls = new CostControls({ cacheBackend: redisBackend });
104
+ ```
105
+
106
+ ### @upstash/redis
107
+
108
+ ```typescript
109
+ import { Redis } from '@upstash/redis';
110
+ import { CostControls, CacheBackend } from 'ai-cost-controls';
111
+
112
+ const redis = new Redis({ url: '...', token: '...' });
113
+
114
+ const upstashBackend: CacheBackend = {
115
+ async get(key) {
116
+ return redis.get<string>(key);
117
+ },
118
+ async set(key, value, ttlMs) {
119
+ await redis.set(key, value, { px: ttlMs });
120
+ },
121
+ };
122
+
123
+ const controls = new CostControls({ cacheBackend: upstashBackend });
124
+ ```
125
+
126
+ ### Cloudflare KV
127
+
128
+ ```typescript
129
+ import { CacheBackend } from 'ai-cost-controls';
130
+
131
+ // In a Cloudflare Worker
132
+ const kvBackend: CacheBackend = {
133
+ async get(key) {
134
+ return env.AI_CACHE.get(key);
135
+ },
136
+ async set(key, value, ttlMs) {
137
+ await env.AI_CACHE.put(key, value, { expirationTtl: Math.ceil(ttlMs / 1000) });
138
+ },
139
+ };
140
+ ```
141
+
142
+ ## Framework Integration
143
+
144
+ ### Vercel AI SDK
145
+
146
+ ```typescript
147
+ import { streamText } from 'ai';
148
+ import { CostControls } from 'ai-cost-controls';
149
+
150
+ const controls = new CostControls();
151
+
152
+ async function chat(userId: string, message: string) {
153
+ if (!(await controls.checkRateLimit(userId))) {
154
+ return new Response('Rate limited', { status: 429 });
155
+ }
156
+
157
+ const cached = await controls.getCachedResponse(userId, message);
158
+ if (cached) return new Response(cached);
159
+
160
+ const result = await streamText({ model, messages: [{ role: 'user', content: message }] });
161
+ const text = await result.text;
162
+
163
+ await controls.cacheResponse(userId, message, text);
164
+ await controls.trackTokenUsage(userId, result.usage.promptTokens, result.usage.completionTokens);
165
+
166
+ return new Response(text);
167
+ }
168
+ ```
169
+
170
+ ### Express Middleware
171
+
172
+ ```typescript
173
+ import express from 'express';
174
+ import { CostControls } from 'ai-cost-controls';
175
+
176
+ const controls = new CostControls();
177
+ const app = express();
178
+
179
+ app.use('/ai', async (req, res, next) => {
180
+ const userId = req.user.id;
181
+
182
+ if (!(await controls.checkRateLimit(userId))) {
183
+ return res.status(429).json({ error: 'Rate limited' });
184
+ }
185
+
186
+ next();
187
+ });
188
+ ```
189
+
190
+ ## API Reference
191
+
192
+ ### `new CostControls(options?: CostControlsOptions)`
193
+
194
+ Creates a new instance.
195
+
196
+ ### `checkRateLimit(userId: string): Promise<boolean>`
197
+
198
+ Returns `true` if the request is allowed, `false` if rate-limited.
199
+
200
+ ### `getCachedResponse(userId: string, message: string): Promise<string | null>`
201
+
202
+ Returns a cached response or `null`. Cache keys are case-insensitive and trim-aware.
203
+
204
+ ### `cacheResponse(userId: string, message: string, response: string): Promise<void>`
205
+
206
+ Stores a response in the cache.
207
+
208
+ ### `trackTokenUsage(userId: string, inputTokens: number, outputTokens: number): Promise<boolean>`
209
+
210
+ Tracks token usage. Returns `false` if the daily or monthly budget would be exceeded.
211
+
212
+ ### `getTokenUsage(userId: string, period: 'daily' | 'monthly'): Promise<number>`
213
+
214
+ Returns current token usage for the specified period.
215
+
216
+ ### `getConfig(): Promise<CostControlsConfig>`
217
+
218
+ Returns the resolved configuration (static defaults merged with `configLoader` result).
219
+
220
+ ## License
221
+
222
+ MIT
@@ -0,0 +1,167 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CostControls = void 0;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const in_memory_backend_1 = require("./in-memory-backend");
6
+ const interfaces_1 = require("./interfaces");
7
+ const NOOP_LOGGER = {
8
+ debug() { },
9
+ warn() { }
10
+ };
11
+ /**
12
+ * Framework-agnostic AI cost controls: rate limiting, response caching,
13
+ * and token budget tracking with pluggable cache backends.
14
+ */
15
+ class CostControls {
16
+ constructor(options) {
17
+ /** In-memory rate limit store (always local — not backed by CacheBackend). */
18
+ this.rateLimits = new Map();
19
+ this.backend = options?.cacheBackend ?? new in_memory_backend_1.InMemoryCacheBackend();
20
+ this.configLoader = options?.configLoader;
21
+ this.logger = options?.logger ?? NOOP_LOGGER;
22
+ this.staticConfig = { ...interfaces_1.DEFAULT_CONFIG, ...options?.config };
23
+ }
24
+ /**
25
+ * Resolve config: static defaults merged with configLoader result (if provided).
26
+ */
27
+ async getConfig() {
28
+ if (!this.configLoader) {
29
+ return this.staticConfig;
30
+ }
31
+ const dynamic = await this.configLoader();
32
+ return {
33
+ ...this.staticConfig,
34
+ ...dynamic
35
+ };
36
+ }
37
+ /**
38
+ * Check and enforce per-user rate limiting.
39
+ * Returns true if the request is allowed, false if rate-limited.
40
+ */
41
+ async checkRateLimit(userId) {
42
+ const config = await this.getConfig();
43
+ const now = Date.now();
44
+ const windowMs = 60000;
45
+ let entry = this.rateLimits.get(userId);
46
+ if (!entry) {
47
+ entry = { timestamps: [] };
48
+ this.rateLimits.set(userId, entry);
49
+ }
50
+ entry.timestamps = entry.timestamps.filter((ts) => now - ts < windowMs);
51
+ if (entry.timestamps.length >= config.rateLimitPerMinute) {
52
+ return false;
53
+ }
54
+ entry.timestamps.push(now);
55
+ return true;
56
+ }
57
+ /**
58
+ * Get a cached response for a query, if one exists within the cache TTL.
59
+ */
60
+ async getCachedResponse(userId, message) {
61
+ const cacheKey = this.buildCacheKey(userId, message);
62
+ try {
63
+ const cached = await this.backend.get(cacheKey);
64
+ if (cached) {
65
+ this.logger.debug(`Cache hit for user ${userId}`);
66
+ return cached;
67
+ }
68
+ }
69
+ catch (error) {
70
+ this.logger.warn(`Cache read failed: ${error instanceof Error ? error.message : String(error)}`);
71
+ }
72
+ return null;
73
+ }
74
+ /**
75
+ * Store a response in the cache for future identical queries.
76
+ */
77
+ async cacheResponse(userId, message, response) {
78
+ const config = await this.getConfig();
79
+ const cacheKey = this.buildCacheKey(userId, message);
80
+ const ttlMs = config.cacheTtlSeconds * 1000;
81
+ try {
82
+ await this.backend.set(cacheKey, response, ttlMs);
83
+ }
84
+ catch (error) {
85
+ this.logger.warn(`Cache write failed: ${error instanceof Error ? error.message : String(error)}`);
86
+ }
87
+ }
88
+ /**
89
+ * Track token usage for a user. Returns false if budget is exceeded.
90
+ */
91
+ async trackTokenUsage(userId, inputTokens, outputTokens) {
92
+ const config = await this.getConfig();
93
+ const dailyKey = this.buildTokenKey(userId, 'daily');
94
+ const monthlyKey = this.buildTokenKey(userId, 'monthly');
95
+ const totalTokens = inputTokens + outputTokens;
96
+ // Read current usage
97
+ let dailyUsage = 0;
98
+ let monthlyUsage = 0;
99
+ try {
100
+ const [dailyRaw, monthlyRaw] = await Promise.all([
101
+ this.backend.get(dailyKey),
102
+ this.backend.get(monthlyKey)
103
+ ]);
104
+ dailyUsage = dailyRaw ? parseInt(dailyRaw, 10) : 0;
105
+ monthlyUsage = monthlyRaw ? parseInt(monthlyRaw, 10) : 0;
106
+ }
107
+ catch (error) {
108
+ this.logger.warn(`Token usage read failed: ${error instanceof Error ? error.message : String(error)}`);
109
+ }
110
+ if (dailyUsage + totalTokens > config.dailyTokenBudget) {
111
+ this.logger.warn(`Daily token budget exceeded for user ${userId}`);
112
+ return false;
113
+ }
114
+ if (monthlyUsage + totalTokens > config.monthlyTokenBudget) {
115
+ this.logger.warn(`Monthly token budget exceeded for user ${userId}`);
116
+ return false;
117
+ }
118
+ // Write updated usage with appropriate TTLs
119
+ const endOfDay = new Date();
120
+ endOfDay.setHours(23, 59, 59, 999);
121
+ const dailyTtl = endOfDay.getTime() - Date.now();
122
+ const endOfMonth = new Date();
123
+ endOfMonth.setMonth(endOfMonth.getMonth() + 1, 1);
124
+ endOfMonth.setHours(0, 0, 0, 0);
125
+ const monthlyTtl = endOfMonth.getTime() - Date.now();
126
+ try {
127
+ await Promise.all([
128
+ this.backend.set(dailyKey, String(dailyUsage + totalTokens), dailyTtl),
129
+ this.backend.set(monthlyKey, String(monthlyUsage + totalTokens), monthlyTtl)
130
+ ]);
131
+ }
132
+ catch (error) {
133
+ this.logger.warn(`Token usage write failed: ${error instanceof Error ? error.message : String(error)}`);
134
+ }
135
+ return true;
136
+ }
137
+ /**
138
+ * Get current token usage for a user within the specified period.
139
+ */
140
+ async getTokenUsage(userId, period) {
141
+ const key = this.buildTokenKey(userId, period);
142
+ try {
143
+ const value = await this.backend.get(key);
144
+ return value ? parseInt(value, 10) : 0;
145
+ }
146
+ catch {
147
+ return 0;
148
+ }
149
+ }
150
+ // --- Private helpers ---
151
+ buildCacheKey(userId, message) {
152
+ const hash = (0, node_crypto_1.createHash)('sha256')
153
+ .update(message.toLowerCase().trim())
154
+ .digest('hex')
155
+ .substring(0, 16);
156
+ return `ai-cache:${userId}:${hash}`;
157
+ }
158
+ buildTokenKey(userId, period) {
159
+ const now = new Date();
160
+ if (period === 'daily') {
161
+ return `ai-tokens:${userId}:daily:${now.toISOString().slice(0, 10)}`;
162
+ }
163
+ return `ai-tokens:${userId}:monthly:${now.toISOString().slice(0, 7)}`;
164
+ }
165
+ }
166
+ exports.CostControls = CostControls;
167
+ //# sourceMappingURL=cost-controls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-controls.js","sourceRoot":"","sources":["../../src/cost-controls.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryCacheBackend = void 0;
4
+ /**
5
+ * Zero-dependency in-memory cache backend.
6
+ * Suitable for single-process deployments. For multi-process or serverless,
7
+ * provide a Redis/KV CacheBackend instead.
8
+ */
9
+ class InMemoryCacheBackend {
10
+ constructor(maxSize = 10000) {
11
+ this.store = new Map();
12
+ this.maxSize = maxSize;
13
+ }
14
+ async get(key) {
15
+ const entry = this.store.get(key);
16
+ if (!entry) {
17
+ return null;
18
+ }
19
+ if (entry.expiresAt <= Date.now()) {
20
+ this.store.delete(key);
21
+ return null;
22
+ }
23
+ return entry.value;
24
+ }
25
+ async set(key, value, ttlMs) {
26
+ // Evict oldest entries if at capacity
27
+ if (this.store.size >= this.maxSize && !this.store.has(key)) {
28
+ const firstKey = this.store.keys().next().value;
29
+ if (firstKey !== undefined) {
30
+ this.store.delete(firstKey);
31
+ }
32
+ }
33
+ this.store.set(key, {
34
+ value,
35
+ expiresAt: Date.now() + ttlMs
36
+ });
37
+ }
38
+ /** Number of entries currently stored (including expired). */
39
+ get size() {
40
+ return this.store.size;
41
+ }
42
+ /** Remove all entries. */
43
+ clear() {
44
+ this.store.clear();
45
+ }
46
+ }
47
+ exports.InMemoryCacheBackend = InMemoryCacheBackend;
48
+ //# sourceMappingURL=in-memory-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-backend.js","sourceRoot":"","sources":["../../src/in-memory-backend.ts"],"names":[],"mappings":";;;AAEA;;;;GAIG;AACH,MAAa,oBAAoB;IAI/B,YAAY,OAAO,GAAG,KAAM;QAHX,UAAK,GAAG,IAAI,GAAG,EAAgD,CAAC;QAI/E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAElC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa,EAAE,KAAa;QACjD,sCAAsC;QACtC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,8DAA8D;IAC9D,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,0BAA0B;IAC1B,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF;AA/CD,oDA+CC"}
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = exports.InMemoryCacheBackend = exports.CostControls = void 0;
4
+ var cost_controls_1 = require("./cost-controls");
5
+ Object.defineProperty(exports, "CostControls", { enumerable: true, get: function () { return cost_controls_1.CostControls; } });
6
+ var in_memory_backend_1 = require("./in-memory-backend");
7
+ Object.defineProperty(exports, "InMemoryCacheBackend", { enumerable: true, get: function () { return in_memory_backend_1.InMemoryCacheBackend; } });
8
+ var interfaces_1 = require("./interfaces");
9
+ Object.defineProperty(exports, "DEFAULT_CONFIG", { enumerable: true, get: function () { return interfaces_1.DEFAULT_CONFIG; } });
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;AAAA,iDAA+C;AAAtC,6GAAA,YAAY,OAAA;AACrB,yDAA2D;AAAlD,yHAAA,oBAAoB,OAAA;AAC7B,2CAMsB;AAFpB,4GAAA,cAAc,OAAA"}
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_CONFIG = void 0;
4
+ /** Default configuration values. */
5
+ exports.DEFAULT_CONFIG = {
6
+ rateLimitPerMinute: 20,
7
+ cacheTtlSeconds: 300,
8
+ dailyTokenBudget: 100000,
9
+ monthlyTokenBudget: 2000000
10
+ };
11
+ //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":";;;AAmCA,oCAAoC;AACvB,QAAA,cAAc,GAAuB;IAChD,kBAAkB,EAAE,EAAE;IACtB,eAAe,EAAE,GAAG;IACpB,gBAAgB,EAAE,MAAO;IACzB,kBAAkB,EAAE,OAAS;CAC9B,CAAC"}
@@ -0,0 +1,163 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { InMemoryCacheBackend } from './in-memory-backend';
3
+ import { DEFAULT_CONFIG } from './interfaces';
4
+ const NOOP_LOGGER = {
5
+ debug() { },
6
+ warn() { }
7
+ };
8
+ /**
9
+ * Framework-agnostic AI cost controls: rate limiting, response caching,
10
+ * and token budget tracking with pluggable cache backends.
11
+ */
12
+ export class CostControls {
13
+ constructor(options) {
14
+ /** In-memory rate limit store (always local — not backed by CacheBackend). */
15
+ this.rateLimits = new Map();
16
+ this.backend = options?.cacheBackend ?? new InMemoryCacheBackend();
17
+ this.configLoader = options?.configLoader;
18
+ this.logger = options?.logger ?? NOOP_LOGGER;
19
+ this.staticConfig = { ...DEFAULT_CONFIG, ...options?.config };
20
+ }
21
+ /**
22
+ * Resolve config: static defaults merged with configLoader result (if provided).
23
+ */
24
+ async getConfig() {
25
+ if (!this.configLoader) {
26
+ return this.staticConfig;
27
+ }
28
+ const dynamic = await this.configLoader();
29
+ return {
30
+ ...this.staticConfig,
31
+ ...dynamic
32
+ };
33
+ }
34
+ /**
35
+ * Check and enforce per-user rate limiting.
36
+ * Returns true if the request is allowed, false if rate-limited.
37
+ */
38
+ async checkRateLimit(userId) {
39
+ const config = await this.getConfig();
40
+ const now = Date.now();
41
+ const windowMs = 60000;
42
+ let entry = this.rateLimits.get(userId);
43
+ if (!entry) {
44
+ entry = { timestamps: [] };
45
+ this.rateLimits.set(userId, entry);
46
+ }
47
+ entry.timestamps = entry.timestamps.filter((ts) => now - ts < windowMs);
48
+ if (entry.timestamps.length >= config.rateLimitPerMinute) {
49
+ return false;
50
+ }
51
+ entry.timestamps.push(now);
52
+ return true;
53
+ }
54
+ /**
55
+ * Get a cached response for a query, if one exists within the cache TTL.
56
+ */
57
+ async getCachedResponse(userId, message) {
58
+ const cacheKey = this.buildCacheKey(userId, message);
59
+ try {
60
+ const cached = await this.backend.get(cacheKey);
61
+ if (cached) {
62
+ this.logger.debug(`Cache hit for user ${userId}`);
63
+ return cached;
64
+ }
65
+ }
66
+ catch (error) {
67
+ this.logger.warn(`Cache read failed: ${error instanceof Error ? error.message : String(error)}`);
68
+ }
69
+ return null;
70
+ }
71
+ /**
72
+ * Store a response in the cache for future identical queries.
73
+ */
74
+ async cacheResponse(userId, message, response) {
75
+ const config = await this.getConfig();
76
+ const cacheKey = this.buildCacheKey(userId, message);
77
+ const ttlMs = config.cacheTtlSeconds * 1000;
78
+ try {
79
+ await this.backend.set(cacheKey, response, ttlMs);
80
+ }
81
+ catch (error) {
82
+ this.logger.warn(`Cache write failed: ${error instanceof Error ? error.message : String(error)}`);
83
+ }
84
+ }
85
+ /**
86
+ * Track token usage for a user. Returns false if budget is exceeded.
87
+ */
88
+ async trackTokenUsage(userId, inputTokens, outputTokens) {
89
+ const config = await this.getConfig();
90
+ const dailyKey = this.buildTokenKey(userId, 'daily');
91
+ const monthlyKey = this.buildTokenKey(userId, 'monthly');
92
+ const totalTokens = inputTokens + outputTokens;
93
+ // Read current usage
94
+ let dailyUsage = 0;
95
+ let monthlyUsage = 0;
96
+ try {
97
+ const [dailyRaw, monthlyRaw] = await Promise.all([
98
+ this.backend.get(dailyKey),
99
+ this.backend.get(monthlyKey)
100
+ ]);
101
+ dailyUsage = dailyRaw ? parseInt(dailyRaw, 10) : 0;
102
+ monthlyUsage = monthlyRaw ? parseInt(monthlyRaw, 10) : 0;
103
+ }
104
+ catch (error) {
105
+ this.logger.warn(`Token usage read failed: ${error instanceof Error ? error.message : String(error)}`);
106
+ }
107
+ if (dailyUsage + totalTokens > config.dailyTokenBudget) {
108
+ this.logger.warn(`Daily token budget exceeded for user ${userId}`);
109
+ return false;
110
+ }
111
+ if (monthlyUsage + totalTokens > config.monthlyTokenBudget) {
112
+ this.logger.warn(`Monthly token budget exceeded for user ${userId}`);
113
+ return false;
114
+ }
115
+ // Write updated usage with appropriate TTLs
116
+ const endOfDay = new Date();
117
+ endOfDay.setHours(23, 59, 59, 999);
118
+ const dailyTtl = endOfDay.getTime() - Date.now();
119
+ const endOfMonth = new Date();
120
+ endOfMonth.setMonth(endOfMonth.getMonth() + 1, 1);
121
+ endOfMonth.setHours(0, 0, 0, 0);
122
+ const monthlyTtl = endOfMonth.getTime() - Date.now();
123
+ try {
124
+ await Promise.all([
125
+ this.backend.set(dailyKey, String(dailyUsage + totalTokens), dailyTtl),
126
+ this.backend.set(monthlyKey, String(monthlyUsage + totalTokens), monthlyTtl)
127
+ ]);
128
+ }
129
+ catch (error) {
130
+ this.logger.warn(`Token usage write failed: ${error instanceof Error ? error.message : String(error)}`);
131
+ }
132
+ return true;
133
+ }
134
+ /**
135
+ * Get current token usage for a user within the specified period.
136
+ */
137
+ async getTokenUsage(userId, period) {
138
+ const key = this.buildTokenKey(userId, period);
139
+ try {
140
+ const value = await this.backend.get(key);
141
+ return value ? parseInt(value, 10) : 0;
142
+ }
143
+ catch {
144
+ return 0;
145
+ }
146
+ }
147
+ // --- Private helpers ---
148
+ buildCacheKey(userId, message) {
149
+ const hash = createHash('sha256')
150
+ .update(message.toLowerCase().trim())
151
+ .digest('hex')
152
+ .substring(0, 16);
153
+ return `ai-cache:${userId}:${hash}`;
154
+ }
155
+ buildTokenKey(userId, period) {
156
+ const now = new Date();
157
+ if (period === 'daily') {
158
+ return `ai-tokens:${userId}:daily:${now.toISOString().slice(0, 10)}`;
159
+ }
160
+ return `ai-tokens:${userId}:monthly:${now.toISOString().slice(0, 7)}`;
161
+ }
162
+ }
163
+ //# sourceMappingURL=cost-controls.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-controls.js","sourceRoot":"","sources":["../../src/cost-controls.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Zero-dependency in-memory cache backend.
3
+ * Suitable for single-process deployments. For multi-process or serverless,
4
+ * provide a Redis/KV CacheBackend instead.
5
+ */
6
+ export class InMemoryCacheBackend {
7
+ constructor(maxSize = 10000) {
8
+ this.store = new Map();
9
+ this.maxSize = maxSize;
10
+ }
11
+ async get(key) {
12
+ const entry = this.store.get(key);
13
+ if (!entry) {
14
+ return null;
15
+ }
16
+ if (entry.expiresAt <= Date.now()) {
17
+ this.store.delete(key);
18
+ return null;
19
+ }
20
+ return entry.value;
21
+ }
22
+ async set(key, value, ttlMs) {
23
+ // Evict oldest entries if at capacity
24
+ if (this.store.size >= this.maxSize && !this.store.has(key)) {
25
+ const firstKey = this.store.keys().next().value;
26
+ if (firstKey !== undefined) {
27
+ this.store.delete(firstKey);
28
+ }
29
+ }
30
+ this.store.set(key, {
31
+ value,
32
+ expiresAt: Date.now() + ttlMs
33
+ });
34
+ }
35
+ /** Number of entries currently stored (including expired). */
36
+ get size() {
37
+ return this.store.size;
38
+ }
39
+ /** Remove all entries. */
40
+ clear() {
41
+ this.store.clear();
42
+ }
43
+ }
44
+ //# sourceMappingURL=in-memory-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-backend.js","sourceRoot":"","sources":["../../src/in-memory-backend.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,oBAAoB;IAI/B,YAAY,OAAO,GAAG,KAAM;QAHX,UAAK,GAAG,IAAI,GAAG,EAAgD,CAAC;QAI/E,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAElC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,OAAO,IAAI,CAAC;QACd,CAAC;QAED,IAAI,KAAK,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAClC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACvB,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,KAAK,CAAC,KAAK,CAAC;IACrB,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW,EAAE,KAAa,EAAE,KAAa;QACjD,sCAAsC;QACtC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5D,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;YAChD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;gBAC3B,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;YAC9B,CAAC;QACH,CAAC;QAED,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE;YAClB,KAAK;YACL,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK;SAC9B,CAAC,CAAC;IACL,CAAC;IAED,8DAA8D;IAC9D,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;IACzB,CAAC;IAED,0BAA0B;IAC1B,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ export { CostControls } from './cost-controls';
2
+ export { InMemoryCacheBackend } from './in-memory-backend';
3
+ export { DEFAULT_CONFIG } from './interfaces';
4
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAIL,cAAc,EAEf,MAAM,cAAc,CAAC"}
@@ -0,0 +1,8 @@
1
+ /** Default configuration values. */
2
+ export const DEFAULT_CONFIG = {
3
+ rateLimitPerMinute: 20,
4
+ cacheTtlSeconds: 300,
5
+ dailyTokenBudget: 100000,
6
+ monthlyTokenBudget: 2000000
7
+ };
8
+ //# sourceMappingURL=interfaces.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"AAmCA,oCAAoC;AACpC,MAAM,CAAC,MAAM,cAAc,GAAuB;IAChD,kBAAkB,EAAE,EAAE;IACtB,eAAe,EAAE,GAAG;IACpB,gBAAgB,EAAE,MAAO;IACzB,kBAAkB,EAAE,OAAS;CAC9B,CAAC"}
@@ -0,0 +1,42 @@
1
+ import { CostControlsConfig, CostControlsOptions } from './interfaces';
2
+ /**
3
+ * Framework-agnostic AI cost controls: rate limiting, response caching,
4
+ * and token budget tracking with pluggable cache backends.
5
+ */
6
+ export declare class CostControls {
7
+ private readonly backend;
8
+ private readonly staticConfig;
9
+ private readonly configLoader?;
10
+ private readonly logger;
11
+ /** In-memory rate limit store (always local — not backed by CacheBackend). */
12
+ private readonly rateLimits;
13
+ constructor(options?: CostControlsOptions);
14
+ /**
15
+ * Resolve config: static defaults merged with configLoader result (if provided).
16
+ */
17
+ getConfig(): Promise<CostControlsConfig>;
18
+ /**
19
+ * Check and enforce per-user rate limiting.
20
+ * Returns true if the request is allowed, false if rate-limited.
21
+ */
22
+ checkRateLimit(userId: string): Promise<boolean>;
23
+ /**
24
+ * Get a cached response for a query, if one exists within the cache TTL.
25
+ */
26
+ getCachedResponse(userId: string, message: string): Promise<string | null>;
27
+ /**
28
+ * Store a response in the cache for future identical queries.
29
+ */
30
+ cacheResponse(userId: string, message: string, response: string): Promise<void>;
31
+ /**
32
+ * Track token usage for a user. Returns false if budget is exceeded.
33
+ */
34
+ trackTokenUsage(userId: string, inputTokens: number, outputTokens: number): Promise<boolean>;
35
+ /**
36
+ * Get current token usage for a user within the specified period.
37
+ */
38
+ getTokenUsage(userId: string, period: 'daily' | 'monthly'): Promise<number>;
39
+ private buildCacheKey;
40
+ private buildTokenKey;
41
+ }
42
+ //# sourceMappingURL=cost-controls.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cost-controls.d.ts","sourceRoot":"","sources":["../../src/cost-controls.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,kBAAkB,EAClB,mBAAmB,EAGpB,MAAM,cAAc,CAAC;AAWtB;;;GAGG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAe;IACvC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAqB;IAClD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAA6C;IAC3E,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAEhC,8EAA8E;IAC9E,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAqC;gBAEpD,OAAO,CAAC,EAAE,mBAAmB;IAOzC;;OAEG;IACG,SAAS,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAa9C;;;OAGG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAsBtD;;OAEG;IACG,iBAAiB,CACrB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAmBzB;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,IAAI,CAAC;IAchB;;OAEG;IACG,eAAe,CACnB,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,YAAY,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC;IA6DnB;;OAEG;IACG,aAAa,CACjB,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,OAAO,GAAG,SAAS,GAC1B,OAAO,CAAC,MAAM,CAAC;IAalB,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,aAAa;CAYtB"}
@@ -0,0 +1,18 @@
1
+ import { CacheBackend } from './interfaces';
2
+ /**
3
+ * Zero-dependency in-memory cache backend.
4
+ * Suitable for single-process deployments. For multi-process or serverless,
5
+ * provide a Redis/KV CacheBackend instead.
6
+ */
7
+ export declare class InMemoryCacheBackend implements CacheBackend {
8
+ private readonly store;
9
+ private readonly maxSize;
10
+ constructor(maxSize?: number);
11
+ get(key: string): Promise<string | null>;
12
+ set(key: string, value: string, ttlMs: number): Promise<void>;
13
+ /** Number of entries currently stored (including expired). */
14
+ get size(): number;
15
+ /** Remove all entries. */
16
+ clear(): void;
17
+ }
18
+ //# sourceMappingURL=in-memory-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in-memory-backend.d.ts","sourceRoot":"","sources":["../../src/in-memory-backend.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C;;;;GAIG;AACH,qBAAa,oBAAqB,YAAW,YAAY;IACvD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA2D;IACjF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,SAAS;IAItB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAexC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAenE,8DAA8D;IAC9D,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,0BAA0B;IAC1B,KAAK,IAAI,IAAI;CAGd"}
@@ -0,0 +1,4 @@
1
+ export { CostControls } from './cost-controls';
2
+ export { InMemoryCacheBackend } from './in-memory-backend';
3
+ export { CacheBackend, CostControlsConfig, CostControlsOptions, DEFAULT_CONFIG, Logger } from './interfaces';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,mBAAmB,EACnB,cAAc,EACd,MAAM,EACP,MAAM,cAAc,CAAC"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Pluggable cache backend interface.
3
+ * Implement with ioredis, node-redis, @upstash/redis, Cloudflare KV, etc.
4
+ */
5
+ export interface CacheBackend {
6
+ get(key: string): Promise<string | null>;
7
+ set(key: string, value: string, ttlMs: number): Promise<void>;
8
+ }
9
+ /** Cost controls configuration values. */
10
+ export interface CostControlsConfig {
11
+ rateLimitPerMinute: number;
12
+ cacheTtlSeconds: number;
13
+ dailyTokenBudget: number;
14
+ monthlyTokenBudget: number;
15
+ }
16
+ /** Minimal logger interface — bring your own logger. */
17
+ export interface Logger {
18
+ debug(msg: string): void;
19
+ warn(msg: string): void;
20
+ }
21
+ /** Constructor options for CostControls. */
22
+ export interface CostControlsOptions {
23
+ /** Static config overrides (merged with defaults). */
24
+ config?: Partial<CostControlsConfig>;
25
+ /** Cache backend — defaults to InMemoryCacheBackend. */
26
+ cacheBackend?: CacheBackend;
27
+ /** Dynamic config loader (e.g., from DB). Called on each operation. */
28
+ configLoader?: () => Promise<Partial<CostControlsConfig>>;
29
+ /** Optional logger. */
30
+ logger?: Logger;
31
+ }
32
+ /** Default configuration values. */
33
+ export declare const DEFAULT_CONFIG: CostControlsConfig;
34
+ //# sourceMappingURL=interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../../src/interfaces.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/D;AAED,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,kBAAkB,EAAE,MAAM,CAAC;IAC3B,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,wDAAwD;AACxD,MAAM,WAAW,MAAM;IACrB,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,4CAA4C;AAC5C,MAAM,WAAW,mBAAmB;IAClC,sDAAsD;IACtD,MAAM,CAAC,EAAE,OAAO,CAAC,kBAAkB,CAAC,CAAC;IACrC,wDAAwD;IACxD,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,uEAAuE;IACvE,YAAY,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC1D,uBAAuB;IACvB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,oCAAoC;AACpC,eAAO,MAAM,cAAc,EAAE,kBAK5B,CAAC"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "ai-cost-controls",
3
+ "version": "0.1.0",
4
+ "description": "Framework-agnostic AI cost controls: per-user rate limiting, token budget tracking, and response caching with pluggable backends.",
5
+ "keywords": [
6
+ "ai",
7
+ "cost-controls",
8
+ "rate-limiting",
9
+ "token-budget",
10
+ "caching",
11
+ "llm",
12
+ "openai",
13
+ "anthropic",
14
+ "vercel-ai-sdk"
15
+ ],
16
+ "license": "MIT",
17
+ "author": "Ray Ockenfels",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/roaming-rockenfels/ai-cost-controls.git"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/roaming-rockenfels/ai-cost-controls/issues"
24
+ },
25
+ "homepage": "https://github.com/roaming-rockenfels/ai-cost-controls#readme",
26
+ "main": "./dist/cjs/index.js",
27
+ "module": "./dist/esm/index.js",
28
+ "types": "./dist/types/index.d.ts",
29
+ "exports": {
30
+ ".": {
31
+ "import": {
32
+ "types": "./dist/types/index.d.ts",
33
+ "default": "./dist/esm/index.js"
34
+ },
35
+ "require": {
36
+ "types": "./dist/types/index.d.ts",
37
+ "default": "./dist/cjs/index.js"
38
+ }
39
+ }
40
+ },
41
+ "files": [
42
+ "dist",
43
+ "README.md",
44
+ "LICENSE"
45
+ ],
46
+ "scripts": {
47
+ "build": "npm run build:cjs && npm run build:esm && npm run build:types",
48
+ "build:cjs": "tsc -p tsconfig.cjs.json",
49
+ "build:esm": "tsc -p tsconfig.esm.json",
50
+ "build:types": "tsc -p tsconfig.types.json",
51
+ "test": "jest",
52
+ "lint": "eslint src/ tests/ --ext .ts",
53
+ "typecheck": "tsc --noEmit",
54
+ "prepare": "npm run build",
55
+ "prepublishOnly": "npm test"
56
+ },
57
+ "devDependencies": {
58
+ "@types/jest": "^29.5.0",
59
+ "@types/node": "^22.0.0",
60
+ "jest": "^29.7.0",
61
+ "ts-jest": "^29.1.0",
62
+ "ts-node": "^10.9.2",
63
+ "typescript": "^5.4.0"
64
+ }
65
+ }