paygate-mcp 1.9.0 → 2.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.
@@ -0,0 +1,341 @@
1
+ "use strict";
2
+ /**
3
+ * RedisClient — Minimal zero-dependency Redis client using raw RESP protocol.
4
+ *
5
+ * Implements just enough of the Redis protocol for PayGate's needs:
6
+ * SET, GET, DEL, INCR, INCRBY, DECRBY, EXPIRE, PEXPIRE, EXISTS,
7
+ * HSET, HGET, HGETALL, HDEL, EVALSHA/script execution (Lua),
8
+ * ZADD, ZRANGEBYSCORE, ZREMRANGEBYSCORE, ZCARD,
9
+ * SCAN, KEYS, INFO, PING, AUTH, SELECT.
10
+ *
11
+ * Uses Node.js net.Socket (built-in, zero deps). Supports auth and db selection.
12
+ * Connection is lazy — established on first command.
13
+ *
14
+ * NOTE: The `evalLua` method uses Redis EVAL command to execute server-side
15
+ * Lua scripts for atomic operations. These scripts are hardcoded within
16
+ * PayGate (not user-provided) and are essential for atomic credit deduction
17
+ * and rate limiting in distributed deployments.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.RedisClient = void 0;
21
+ exports.parseRedisUrl = parseRedisUrl;
22
+ const net_1 = require("net");
23
+ /**
24
+ * Parse a redis:// URL into connection options.
25
+ * Format: redis://[:password@]host[:port][/db]
26
+ */
27
+ function parseRedisUrl(url) {
28
+ const parsed = new URL(url);
29
+ return {
30
+ host: parsed.hostname || '127.0.0.1',
31
+ port: parseInt(parsed.port, 10) || 6379,
32
+ password: parsed.password || undefined,
33
+ db: parsed.pathname && parsed.pathname.length > 1
34
+ ? parseInt(parsed.pathname.slice(1), 10) || 0
35
+ : 0,
36
+ };
37
+ }
38
+ class RedisClient {
39
+ socket = null;
40
+ connected = false;
41
+ connecting = false;
42
+ queue = [];
43
+ buffer = Buffer.alloc(0);
44
+ opts;
45
+ closed = false;
46
+ constructor(opts) {
47
+ this.opts = {
48
+ host: opts.host,
49
+ port: opts.port,
50
+ password: opts.password || '',
51
+ db: opts.db || 0,
52
+ connectTimeout: opts.connectTimeout || 5000,
53
+ commandTimeout: opts.commandTimeout || 10000,
54
+ };
55
+ }
56
+ // ─── Public API ────────────────────────────────────────────────────────────
57
+ async ping() {
58
+ return (await this.command('PING'));
59
+ }
60
+ async set(key, value, exSeconds) {
61
+ if (exSeconds && exSeconds > 0) {
62
+ return (await this.command('SET', key, value, 'EX', String(exSeconds)));
63
+ }
64
+ return (await this.command('SET', key, value));
65
+ }
66
+ async get(key) {
67
+ return (await this.command('GET', key));
68
+ }
69
+ async del(...keys) {
70
+ return (await this.command('DEL', ...keys));
71
+ }
72
+ async incr(key) {
73
+ return (await this.command('INCR', key));
74
+ }
75
+ async incrby(key, amount) {
76
+ return (await this.command('INCRBY', key, String(amount)));
77
+ }
78
+ async decrby(key, amount) {
79
+ return (await this.command('DECRBY', key, String(amount)));
80
+ }
81
+ async exists(key) {
82
+ return (await this.command('EXISTS', key));
83
+ }
84
+ async expire(key, seconds) {
85
+ return (await this.command('EXPIRE', key, String(seconds)));
86
+ }
87
+ async pexpire(key, ms) {
88
+ return (await this.command('PEXPIRE', key, String(ms)));
89
+ }
90
+ // ─── Hash commands ─────────────────────────────────────────────────────────
91
+ async hset(key, ...fieldValues) {
92
+ return (await this.command('HSET', key, ...fieldValues));
93
+ }
94
+ async hget(key, field) {
95
+ return (await this.command('HGET', key, field));
96
+ }
97
+ async hgetall(key) {
98
+ const reply = (await this.command('HGETALL', key));
99
+ const result = {};
100
+ if (Array.isArray(reply)) {
101
+ for (let i = 0; i < reply.length; i += 2) {
102
+ result[reply[i]] = reply[i + 1];
103
+ }
104
+ }
105
+ return result;
106
+ }
107
+ async hdel(key, ...fields) {
108
+ return (await this.command('HDEL', key, ...fields));
109
+ }
110
+ // ─── Sorted set commands ───────────────────────────────────────────────────
111
+ async zadd(key, score, member) {
112
+ return (await this.command('ZADD', key, String(score), member));
113
+ }
114
+ async zrangebyscore(key, min, max) {
115
+ return (await this.command('ZRANGEBYSCORE', key, String(min), String(max)));
116
+ }
117
+ async zremrangebyscore(key, min, max) {
118
+ return (await this.command('ZREMRANGEBYSCORE', key, String(min), String(max)));
119
+ }
120
+ async zcard(key) {
121
+ return (await this.command('ZCARD', key));
122
+ }
123
+ // ─── Lua scripting (for atomic operations) ─────────────────────────────────
124
+ /**
125
+ * Execute a server-side Lua script atomically. Used internally by PayGate
126
+ * for atomic credit deduction and rate limiting. Scripts are hardcoded
127
+ * constants, never user-provided input.
128
+ */
129
+ async evalLua(script, numkeys, ...keysAndArgs) {
130
+ return this.command('EVAL', script, String(numkeys), ...keysAndArgs);
131
+ }
132
+ // ─── Scan ──────────────────────────────────────────────────────────────────
133
+ async scan(cursor, pattern, count = 100) {
134
+ const reply = (await this.command('SCAN', cursor, 'MATCH', pattern, 'COUNT', String(count)));
135
+ return reply;
136
+ }
137
+ /**
138
+ * Scan all keys matching a pattern. Iterates until cursor returns "0".
139
+ */
140
+ async scanAll(pattern, count = 100) {
141
+ const keys = [];
142
+ let cursor = '0';
143
+ do {
144
+ const [next, batch] = await this.scan(cursor, pattern, count);
145
+ cursor = next;
146
+ keys.push(...batch);
147
+ } while (cursor !== '0');
148
+ return keys;
149
+ }
150
+ // ─── Connection management ─────────────────────────────────────────────────
151
+ async connect() {
152
+ if (this.connected)
153
+ return;
154
+ if (this.connecting) {
155
+ return new Promise((resolve, reject) => {
156
+ const check = setInterval(() => {
157
+ if (this.connected) {
158
+ clearInterval(check);
159
+ resolve();
160
+ }
161
+ if (this.closed) {
162
+ clearInterval(check);
163
+ reject(new Error('Connection closed'));
164
+ }
165
+ }, 50);
166
+ });
167
+ }
168
+ this.connecting = true;
169
+ return new Promise((resolve, reject) => {
170
+ const timer = setTimeout(() => {
171
+ reject(new Error(`Redis connect timeout (${this.opts.connectTimeout}ms)`));
172
+ this.connecting = false;
173
+ }, this.opts.connectTimeout);
174
+ this.socket = new net_1.Socket();
175
+ this.socket.setNoDelay(true);
176
+ this.socket.on('data', (chunk) => {
177
+ this.buffer = Buffer.concat([this.buffer, chunk]);
178
+ this.processBuffer();
179
+ });
180
+ this.socket.on('error', (err) => {
181
+ clearTimeout(timer);
182
+ if (!this.connected) {
183
+ this.connecting = false;
184
+ reject(err);
185
+ }
186
+ // Reject all pending commands
187
+ for (const cmd of this.queue) {
188
+ cmd.reject(err);
189
+ if (cmd.timer)
190
+ clearTimeout(cmd.timer);
191
+ }
192
+ this.queue = [];
193
+ });
194
+ this.socket.on('close', () => {
195
+ this.connected = false;
196
+ this.connecting = false;
197
+ });
198
+ this.socket.connect(this.opts.port, this.opts.host, async () => {
199
+ clearTimeout(timer);
200
+ this.connected = true;
201
+ this.connecting = false;
202
+ try {
203
+ // AUTH if password provided
204
+ if (this.opts.password) {
205
+ await this.command('AUTH', this.opts.password);
206
+ }
207
+ // SELECT db if non-zero
208
+ if (this.opts.db > 0) {
209
+ await this.command('SELECT', String(this.opts.db));
210
+ }
211
+ resolve();
212
+ }
213
+ catch (err) {
214
+ reject(err);
215
+ }
216
+ });
217
+ });
218
+ }
219
+ async disconnect() {
220
+ this.closed = true;
221
+ if (this.socket) {
222
+ this.socket.destroy();
223
+ this.socket = null;
224
+ }
225
+ this.connected = false;
226
+ this.connecting = false;
227
+ // Reject pending commands
228
+ for (const cmd of this.queue) {
229
+ cmd.reject(new Error('Disconnected'));
230
+ if (cmd.timer)
231
+ clearTimeout(cmd.timer);
232
+ }
233
+ this.queue = [];
234
+ }
235
+ get isConnected() {
236
+ return this.connected;
237
+ }
238
+ // ─── Core command execution ────────────────────────────────────────────────
239
+ async command(...args) {
240
+ if (!this.connected && !this.connecting) {
241
+ await this.connect();
242
+ }
243
+ return new Promise((resolve, reject) => {
244
+ const timer = setTimeout(() => {
245
+ reject(new Error(`Redis command timeout: ${args[0]}`));
246
+ }, this.opts.commandTimeout);
247
+ this.queue.push({ resolve, reject, timer });
248
+ // Encode RESP array
249
+ const encoded = this.encodeCommand(args);
250
+ this.socket.write(encoded);
251
+ });
252
+ }
253
+ // ─── RESP protocol encoding ────────────────────────────────────────────────
254
+ encodeCommand(args) {
255
+ let resp = `*${args.length}\r\n`;
256
+ for (const arg of args) {
257
+ const buf = Buffer.from(arg);
258
+ resp += `$${buf.length}\r\n${arg}\r\n`;
259
+ }
260
+ return resp;
261
+ }
262
+ // ─── RESP protocol decoding ────────────────────────────────────────────────
263
+ processBuffer() {
264
+ while (this.buffer.length > 0 && this.queue.length > 0) {
265
+ const result = this.parseReply(this.buffer, 0);
266
+ if (result === null)
267
+ break; // Incomplete data, wait for more
268
+ const [reply, consumed] = result;
269
+ this.buffer = this.buffer.subarray(consumed);
270
+ const cmd = this.queue.shift();
271
+ if (cmd.timer)
272
+ clearTimeout(cmd.timer);
273
+ if (reply instanceof Error) {
274
+ cmd.reject(reply);
275
+ }
276
+ else {
277
+ cmd.resolve(reply);
278
+ }
279
+ }
280
+ }
281
+ /**
282
+ * Parse a RESP reply from the buffer starting at offset.
283
+ * Returns [parsed_value, bytes_consumed] or null if incomplete.
284
+ */
285
+ parseReply(buf, offset) {
286
+ if (offset >= buf.length)
287
+ return null;
288
+ const type = String.fromCharCode(buf[offset]);
289
+ const lineEnd = this.findCRLF(buf, offset);
290
+ if (lineEnd === -1)
291
+ return null;
292
+ const line = buf.subarray(offset + 1, lineEnd).toString();
293
+ const consumed = lineEnd + 2; // past \r\n
294
+ switch (type) {
295
+ case '+': // Simple string
296
+ return [line, consumed];
297
+ case '-': // Error
298
+ return [new Error(line), consumed];
299
+ case ':': // Integer
300
+ return [parseInt(line, 10), consumed];
301
+ case '$': { // Bulk string
302
+ const len = parseInt(line, 10);
303
+ if (len === -1)
304
+ return [null, consumed];
305
+ if (consumed + len + 2 > buf.length)
306
+ return null; // Incomplete
307
+ const data = buf.subarray(consumed, consumed + len).toString();
308
+ return [data, consumed + len + 2]; // +2 for trailing \r\n
309
+ }
310
+ case '*': { // Array
311
+ const count = parseInt(line, 10);
312
+ if (count === -1)
313
+ return [null, consumed];
314
+ const items = [];
315
+ let pos = consumed;
316
+ for (let i = 0; i < count; i++) {
317
+ const item = this.parseReply(buf, pos);
318
+ if (item === null)
319
+ return null; // Incomplete
320
+ const [val, itemConsumed] = item;
321
+ if (val instanceof Error)
322
+ return [val, itemConsumed];
323
+ items.push(val);
324
+ pos = itemConsumed;
325
+ }
326
+ return [items, pos];
327
+ }
328
+ default:
329
+ return [new Error(`Unknown RESP type: ${type}`), consumed];
330
+ }
331
+ }
332
+ findCRLF(buf, start) {
333
+ for (let i = start; i < buf.length - 1; i++) {
334
+ if (buf[i] === 0x0d && buf[i + 1] === 0x0a)
335
+ return i;
336
+ }
337
+ return -1;
338
+ }
339
+ }
340
+ exports.RedisClient = RedisClient;
341
+ //# sourceMappingURL=redis-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-client.js","sourceRoot":"","sources":["../src/redis-client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,92 @@
1
+ /**
2
+ * RedisSync — Write-through cache adapter for distributed PayGate deployments.
3
+ *
4
+ * Keeps the existing in-memory KeyStore as the fast sync read path, while
5
+ * propagating all writes to Redis for cross-process shared state. On startup,
6
+ * loads existing state from Redis. Periodically refreshes local state from Redis
7
+ * to pick up changes made by other PayGate processes.
8
+ *
9
+ * Architecture:
10
+ * Gate.evaluate() → reads from local KeyStore (sync, fast)
11
+ * KeyStore.save() → writes to memory + triggers Redis write (async, fire-and-forget)
12
+ * RedisPersistence.refresh() → pulls latest state from Redis → updates local KeyStore
13
+ *
14
+ * For credit deduction, uses a Redis Lua script to ensure atomicity across
15
+ * multiple PayGate instances (prevents double-spend race conditions).
16
+ */
17
+ import { RedisClient } from './redis-client';
18
+ import { KeyStore } from './store';
19
+ import { ApiKeyRecord, UsageEvent } from './types';
20
+ export declare class RedisSync {
21
+ private readonly redis;
22
+ private readonly store;
23
+ private syncInterval;
24
+ private readonly syncMs;
25
+ constructor(redis: RedisClient, store: KeyStore, syncIntervalMs?: number);
26
+ /**
27
+ * Initialize: connect to Redis and load existing state into local KeyStore.
28
+ */
29
+ init(): Promise<void>;
30
+ destroy(): Promise<void>;
31
+ /**
32
+ * Save a single key record to Redis. Called after local KeyStore mutations.
33
+ * Fire-and-forget (errors logged, not thrown).
34
+ */
35
+ saveKey(record: ApiKeyRecord): Promise<void>;
36
+ /**
37
+ * Save ALL keys to Redis. Called when local KeyStore.save() fires.
38
+ */
39
+ saveAll(): Promise<void>;
40
+ /**
41
+ * Atomic credit deduction via Redis Lua. Returns true if deduction succeeded.
42
+ * Call this INSTEAD of store.deductCredits() in Redis mode.
43
+ */
44
+ atomicDeduct(apiKey: string, amount: number): Promise<boolean>;
45
+ /**
46
+ * Atomic credit top-up via Redis.
47
+ */
48
+ atomicTopup(apiKey: string, amount: number): Promise<boolean>;
49
+ /**
50
+ * Revoke a key in both local store and Redis.
51
+ */
52
+ revokeKey(apiKey: string): Promise<boolean>;
53
+ /**
54
+ * Record a usage event to Redis (append to list). Fire-and-forget.
55
+ * Events are stored as JSON strings in a Redis list with max 100k entries.
56
+ */
57
+ recordUsage(event: UsageEvent): Promise<void>;
58
+ /**
59
+ * Get usage events from Redis. Returns events after `since` timestamp if provided.
60
+ */
61
+ getUsageEvents(since?: string): Promise<UsageEvent[]>;
62
+ /**
63
+ * Get the count of usage events in Redis.
64
+ */
65
+ getUsageCount(): Promise<number>;
66
+ /**
67
+ * Atomic rate limit check + record via Redis. Returns a result compatible with
68
+ * the local RateLimiter interface. Uses a sorted set per rate-limit key with
69
+ * timestamps as scores for O(log N) sliding window.
70
+ *
71
+ * @param key Composite key (e.g. "pg_abc" or "pg_abc:tool:search")
72
+ * @param maxCalls Maximum calls per window
73
+ * @param windowMs Window size in milliseconds (default: 60000)
74
+ */
75
+ checkRateLimit(key: string, maxCalls: number, windowMs?: number): Promise<{
76
+ allowed: boolean;
77
+ remaining: number;
78
+ resetInMs: number;
79
+ }>;
80
+ /**
81
+ * Load all keys from Redis into the local in-memory KeyStore.
82
+ * Merges with any locally-created keys (doesn't overwrite local-only keys).
83
+ */
84
+ private loadFromRedis;
85
+ /**
86
+ * Push local KeyStore state to Redis (used when Redis is empty on first connect).
87
+ */
88
+ private pushLocalToRedis;
89
+ private recordToHash;
90
+ private hashToRecord;
91
+ }
92
+ //# sourceMappingURL=redis-sync.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-sync.d.ts","sourceRoot":"","sources":["../src/redis-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,YAAY,EAAe,UAAU,EAAE,MAAM,SAAS,CAAC;AA8DhE,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;IACpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAW;IACjC,OAAO,CAAC,YAAY,CAA+C;IACnE,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;gBAEpB,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,EAAE,cAAc,SAAO;IAQtE;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAerB,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAU9B;;;OAGG;IACG,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlD;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAiB9B;;;OAGG;IACG,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IA4BpE;;OAEG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAuBnE;;OAEG;IACG,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYjD;;;OAGG;IACG,WAAW,CAAC,KAAK,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC;IAUnD;;OAEG;IACG,cAAc,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAa3D;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,MAAM,CAAC;IAWtC;;;;;;;;OAQG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAS,GAAG,OAAO,CAAC;QAC9E,OAAO,EAAE,OAAO,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;IA8CF;;;OAGG;YACW,aAAa;IA+B3B;;OAEG;YACW,gBAAgB;IAc9B,OAAO,CAAC,YAAY;IA0BpB,OAAO,CAAC,YAAY;CA0BrB"}