recker 1.0.10 → 1.0.11-alpha.d342d95
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/cli/index.js +54 -0
- package/dist/mcp/index.d.ts +1 -0
- package/dist/mcp/index.d.ts.map +1 -1
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/server.d.ts +30 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +370 -0
- package/package.json +1 -1
- package/dist/plugins/rate-limit.d.ts +0 -8
- package/dist/plugins/rate-limit.d.ts.map +0 -1
- package/dist/plugins/rate-limit.js +0 -57
- package/dist/utils/logger.d.ts +0 -33
- package/dist/utils/logger.d.ts.map +0 -1
- package/dist/utils/logger.js +0 -160
- package/dist/utils/status-codes.d.ts +0 -84
- package/dist/utils/status-codes.d.ts.map +0 -1
- package/dist/utils/status-codes.js +0 -204
- package/dist/utils/task-pool.d.ts +0 -38
- package/dist/utils/task-pool.js +0 -104
package/dist/cli/index.js
CHANGED
|
@@ -326,6 +326,60 @@ ${pc.bold(pc.yellow('Examples:'))}
|
|
|
326
326
|
const { startLoadDashboard } = await import('./tui/load-dashboard.js');
|
|
327
327
|
await startLoadDashboard({ url, users, duration, mode, http2, rampUp });
|
|
328
328
|
});
|
|
329
|
+
program
|
|
330
|
+
.command('mcp')
|
|
331
|
+
.description('Start MCP server for AI agents to access Recker documentation')
|
|
332
|
+
.option('-p, --port <number>', 'Server port', '3100')
|
|
333
|
+
.option('-d, --docs <path>', 'Path to documentation folder')
|
|
334
|
+
.option('--debug', 'Enable debug logging')
|
|
335
|
+
.addHelpText('after', `
|
|
336
|
+
${pc.bold(pc.yellow('Usage:'))}
|
|
337
|
+
${pc.green('$ rek mcp')} ${pc.gray('Start server on port 3100')}
|
|
338
|
+
${pc.green('$ rek mcp -p 8080')} ${pc.gray('Start on custom port')}
|
|
339
|
+
${pc.green('$ rek mcp --debug')} ${pc.gray('Enable debug logging')}
|
|
340
|
+
|
|
341
|
+
${pc.bold(pc.yellow('Tools provided:'))}
|
|
342
|
+
${pc.cyan('search_docs')} Search documentation by keyword
|
|
343
|
+
${pc.cyan('get_doc')} Get full content of a doc file
|
|
344
|
+
|
|
345
|
+
${pc.bold(pc.yellow('Claude Code config (~/.claude.json):'))}
|
|
346
|
+
${pc.gray(`{
|
|
347
|
+
"mcpServers": {
|
|
348
|
+
"recker-docs": {
|
|
349
|
+
"command": "npx",
|
|
350
|
+
"args": ["recker", "mcp"]
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}`)}
|
|
354
|
+
`)
|
|
355
|
+
.action(async (options) => {
|
|
356
|
+
const { MCPServer } = await import('../mcp/server.js');
|
|
357
|
+
const server = new MCPServer({
|
|
358
|
+
port: parseInt(options.port),
|
|
359
|
+
docsPath: options.docs,
|
|
360
|
+
debug: options.debug,
|
|
361
|
+
});
|
|
362
|
+
await server.start();
|
|
363
|
+
console.log(pc.green(`
|
|
364
|
+
┌─────────────────────────────────────────────┐
|
|
365
|
+
│ ${pc.bold('Recker MCP Server')} │
|
|
366
|
+
├─────────────────────────────────────────────┤
|
|
367
|
+
│ Endpoint: ${pc.cyan(`http://localhost:${options.port}`)} │
|
|
368
|
+
│ Docs indexed: ${pc.yellow(String(server.getDocsCount()).padEnd(28))}│
|
|
369
|
+
│ │
|
|
370
|
+
│ Tools: │
|
|
371
|
+
│ • ${pc.cyan('search_docs')} - Search documentation │
|
|
372
|
+
│ • ${pc.cyan('get_doc')} - Get full doc content │
|
|
373
|
+
│ │
|
|
374
|
+
│ Press ${pc.bold('Ctrl+C')} to stop │
|
|
375
|
+
└─────────────────────────────────────────────┘
|
|
376
|
+
`));
|
|
377
|
+
process.on('SIGINT', async () => {
|
|
378
|
+
console.log(pc.yellow('\nShutting down MCP server...'));
|
|
379
|
+
await server.stop();
|
|
380
|
+
process.exit(0);
|
|
381
|
+
});
|
|
382
|
+
});
|
|
329
383
|
program.parse();
|
|
330
384
|
}
|
|
331
385
|
main().catch((error) => {
|
package/dist/mcp/index.d.ts
CHANGED
package/dist/mcp/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAKA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface MCPServerOptions {
|
|
2
|
+
name?: string;
|
|
3
|
+
version?: string;
|
|
4
|
+
docsPath?: string;
|
|
5
|
+
port?: number;
|
|
6
|
+
debug?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare class MCPServer {
|
|
9
|
+
private options;
|
|
10
|
+
private server?;
|
|
11
|
+
private docsIndex;
|
|
12
|
+
constructor(options?: MCPServerOptions);
|
|
13
|
+
private findDocsPath;
|
|
14
|
+
private buildIndex;
|
|
15
|
+
private walkDir;
|
|
16
|
+
private extractTitle;
|
|
17
|
+
private extractKeywords;
|
|
18
|
+
private getTools;
|
|
19
|
+
private handleToolCall;
|
|
20
|
+
private searchDocs;
|
|
21
|
+
private extractSnippet;
|
|
22
|
+
private getDoc;
|
|
23
|
+
private handleRequest;
|
|
24
|
+
start(): Promise<void>;
|
|
25
|
+
stop(): Promise<void>;
|
|
26
|
+
getPort(): number;
|
|
27
|
+
getDocsCount(): number;
|
|
28
|
+
}
|
|
29
|
+
export declare function createMCPServer(options?: MCPServerOptions): MCPServer;
|
|
30
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAcA,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAiBD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAA6B;IAC5C,OAAO,CAAC,MAAM,CAAC,CAAkC;IACjD,OAAO,CAAC,SAAS,CAAkB;gBAEvB,OAAO,GAAE,gBAAqB;IAY1C,OAAO,CAAC,YAAY;IAkBpB,OAAO,CAAC,UAAU;IAuClB,OAAO,CAAC,OAAO;IAyBf,OAAO,CAAC,YAAY;IAKpB,OAAO,CAAC,eAAe;IAyBvB,OAAO,CAAC,QAAQ;IAyChB,OAAO,CAAC,cAAc;IActB,OAAO,CAAC,UAAU;IA4DlB,OAAO,CAAC,cAAc;IAoBtB,OAAO,CAAC,MAAM;IAmCd,OAAO,CAAC,aAAa;IAiDf,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA4DtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAU3B,OAAO,IAAI,MAAM;IAIjB,YAAY,IAAI,MAAM;CAGvB;AAKD,wBAAgB,eAAe,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAErE"}
|
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
import { createServer } from 'http';
|
|
2
|
+
import { readFileSync, readdirSync, statSync, existsSync } from 'fs';
|
|
3
|
+
import { join, relative } from 'path';
|
|
4
|
+
export class MCPServer {
|
|
5
|
+
options;
|
|
6
|
+
server;
|
|
7
|
+
docsIndex = [];
|
|
8
|
+
constructor(options = {}) {
|
|
9
|
+
this.options = {
|
|
10
|
+
name: options.name || 'recker-docs',
|
|
11
|
+
version: options.version || '1.0.0',
|
|
12
|
+
docsPath: options.docsPath || this.findDocsPath(),
|
|
13
|
+
port: options.port || 3100,
|
|
14
|
+
debug: options.debug || false,
|
|
15
|
+
};
|
|
16
|
+
this.buildIndex();
|
|
17
|
+
}
|
|
18
|
+
findDocsPath() {
|
|
19
|
+
const possiblePaths = [
|
|
20
|
+
join(process.cwd(), 'docs'),
|
|
21
|
+
join(process.cwd(), '..', 'docs'),
|
|
22
|
+
join(__dirname, '..', '..', 'docs'),
|
|
23
|
+
join(__dirname, '..', '..', '..', 'docs'),
|
|
24
|
+
];
|
|
25
|
+
for (const p of possiblePaths) {
|
|
26
|
+
if (existsSync(p)) {
|
|
27
|
+
return p;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return join(process.cwd(), 'docs');
|
|
31
|
+
}
|
|
32
|
+
buildIndex() {
|
|
33
|
+
if (!existsSync(this.options.docsPath)) {
|
|
34
|
+
if (this.options.debug) {
|
|
35
|
+
console.log(`[MCP] Docs path not found: ${this.options.docsPath}`);
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
const files = this.walkDir(this.options.docsPath);
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
if (!file.endsWith('.md'))
|
|
42
|
+
continue;
|
|
43
|
+
try {
|
|
44
|
+
const content = readFileSync(file, 'utf-8');
|
|
45
|
+
const relativePath = relative(this.options.docsPath, file);
|
|
46
|
+
const category = relativePath.split('/')[0] || 'root';
|
|
47
|
+
const title = this.extractTitle(content) || relativePath;
|
|
48
|
+
const keywords = this.extractKeywords(content);
|
|
49
|
+
this.docsIndex.push({
|
|
50
|
+
path: relativePath,
|
|
51
|
+
title,
|
|
52
|
+
category,
|
|
53
|
+
content,
|
|
54
|
+
keywords,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
if (this.options.debug) {
|
|
59
|
+
console.log(`[MCP] Failed to index ${file}:`, err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
if (this.options.debug) {
|
|
64
|
+
console.log(`[MCP] Indexed ${this.docsIndex.length} documentation files`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
walkDir(dir) {
|
|
68
|
+
const files = [];
|
|
69
|
+
try {
|
|
70
|
+
const entries = readdirSync(dir);
|
|
71
|
+
for (const entry of entries) {
|
|
72
|
+
if (entry.startsWith('_') || entry.startsWith('.'))
|
|
73
|
+
continue;
|
|
74
|
+
const fullPath = join(dir, entry);
|
|
75
|
+
const stat = statSync(fullPath);
|
|
76
|
+
if (stat.isDirectory()) {
|
|
77
|
+
files.push(...this.walkDir(fullPath));
|
|
78
|
+
}
|
|
79
|
+
else if (stat.isFile()) {
|
|
80
|
+
files.push(fullPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch {
|
|
85
|
+
}
|
|
86
|
+
return files;
|
|
87
|
+
}
|
|
88
|
+
extractTitle(content) {
|
|
89
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
90
|
+
return match ? match[1].trim() : '';
|
|
91
|
+
}
|
|
92
|
+
extractKeywords(content) {
|
|
93
|
+
const keywords = new Set();
|
|
94
|
+
const headings = content.match(/^#{1,3}\s+(.+)$/gm) || [];
|
|
95
|
+
for (const h of headings) {
|
|
96
|
+
keywords.add(h.replace(/^#+\s+/, '').toLowerCase());
|
|
97
|
+
}
|
|
98
|
+
const codePatterns = content.match(/`([a-zA-Z_][a-zA-Z0-9_]*(?:\(\))?)`/g) || [];
|
|
99
|
+
for (const c of codePatterns) {
|
|
100
|
+
keywords.add(c.replace(/`/g, '').toLowerCase());
|
|
101
|
+
}
|
|
102
|
+
const terms = content.match(/\b[A-Z][a-zA-Z]+(?:Client|Server|Error|Response|Request|Plugin|Transport)\b/g) || [];
|
|
103
|
+
for (const t of terms) {
|
|
104
|
+
keywords.add(t.toLowerCase());
|
|
105
|
+
}
|
|
106
|
+
return Array.from(keywords).slice(0, 50);
|
|
107
|
+
}
|
|
108
|
+
getTools() {
|
|
109
|
+
return [
|
|
110
|
+
{
|
|
111
|
+
name: 'search_docs',
|
|
112
|
+
description: 'Search Recker documentation by keyword. Returns matching doc files with titles and snippets. Use this first to find relevant documentation.',
|
|
113
|
+
inputSchema: {
|
|
114
|
+
type: 'object',
|
|
115
|
+
properties: {
|
|
116
|
+
query: {
|
|
117
|
+
type: 'string',
|
|
118
|
+
description: 'Search query (e.g., "retry", "cache", "streaming", "websocket")',
|
|
119
|
+
},
|
|
120
|
+
category: {
|
|
121
|
+
type: 'string',
|
|
122
|
+
description: 'Optional: filter by category (http, cli, ai, protocols, reference, guides)',
|
|
123
|
+
},
|
|
124
|
+
limit: {
|
|
125
|
+
type: 'number',
|
|
126
|
+
description: 'Max results to return (default: 5)',
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
required: ['query'],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: 'get_doc',
|
|
134
|
+
description: 'Get the full content of a specific documentation file. Use the path from search_docs results.',
|
|
135
|
+
inputSchema: {
|
|
136
|
+
type: 'object',
|
|
137
|
+
properties: {
|
|
138
|
+
path: {
|
|
139
|
+
type: 'string',
|
|
140
|
+
description: 'Documentation file path (e.g., "http/07-resilience.md", "cli/01-overview.md")',
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
required: ['path'],
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
];
|
|
147
|
+
}
|
|
148
|
+
handleToolCall(name, args) {
|
|
149
|
+
switch (name) {
|
|
150
|
+
case 'search_docs':
|
|
151
|
+
return this.searchDocs(args);
|
|
152
|
+
case 'get_doc':
|
|
153
|
+
return this.getDoc(args);
|
|
154
|
+
default:
|
|
155
|
+
return {
|
|
156
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
157
|
+
isError: true,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
searchDocs(args) {
|
|
162
|
+
const query = String(args.query || '').toLowerCase();
|
|
163
|
+
const category = args.category ? String(args.category).toLowerCase() : null;
|
|
164
|
+
const limit = Math.min(Number(args.limit) || 5, 10);
|
|
165
|
+
if (!query) {
|
|
166
|
+
return {
|
|
167
|
+
content: [{ type: 'text', text: 'Error: query is required' }],
|
|
168
|
+
isError: true,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
const results = [];
|
|
172
|
+
for (const doc of this.docsIndex) {
|
|
173
|
+
if (category && !doc.category.toLowerCase().includes(category)) {
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
let score = 0;
|
|
177
|
+
const queryTerms = query.split(/\s+/);
|
|
178
|
+
for (const term of queryTerms) {
|
|
179
|
+
if (doc.title.toLowerCase().includes(term))
|
|
180
|
+
score += 10;
|
|
181
|
+
if (doc.path.toLowerCase().includes(term))
|
|
182
|
+
score += 5;
|
|
183
|
+
if (doc.keywords.some(k => k.includes(term)))
|
|
184
|
+
score += 3;
|
|
185
|
+
if (doc.content.toLowerCase().includes(term))
|
|
186
|
+
score += 1;
|
|
187
|
+
}
|
|
188
|
+
if (score > 0) {
|
|
189
|
+
const snippet = this.extractSnippet(doc.content, query);
|
|
190
|
+
results.push({ doc, score, snippet });
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
results.sort((a, b) => b.score - a.score);
|
|
194
|
+
const topResults = results.slice(0, limit);
|
|
195
|
+
if (topResults.length === 0) {
|
|
196
|
+
return {
|
|
197
|
+
content: [{
|
|
198
|
+
type: 'text',
|
|
199
|
+
text: `No documentation found for "${query}". Try different keywords like: http, cache, retry, streaming, websocket, ai, cli, plugins`,
|
|
200
|
+
}],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
const output = topResults.map((r, i) => `${i + 1}. **${r.doc.title}**\n Path: \`${r.doc.path}\`\n Category: ${r.doc.category}\n ${r.snippet}`).join('\n\n');
|
|
204
|
+
return {
|
|
205
|
+
content: [{
|
|
206
|
+
type: 'text',
|
|
207
|
+
text: `Found ${topResults.length} result(s) for "${query}":\n\n${output}\n\nUse get_doc with the path to read full content.`,
|
|
208
|
+
}],
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
extractSnippet(content, query) {
|
|
212
|
+
const lowerContent = content.toLowerCase();
|
|
213
|
+
const index = lowerContent.indexOf(query.split(/\s+/)[0]);
|
|
214
|
+
if (index === -1) {
|
|
215
|
+
const firstPara = content.split('\n\n')[1] || content.substring(0, 200);
|
|
216
|
+
return firstPara.substring(0, 150).trim() + '...';
|
|
217
|
+
}
|
|
218
|
+
const start = Math.max(0, index - 50);
|
|
219
|
+
const end = Math.min(content.length, index + 150);
|
|
220
|
+
let snippet = content.substring(start, end).trim();
|
|
221
|
+
if (start > 0)
|
|
222
|
+
snippet = '...' + snippet;
|
|
223
|
+
if (end < content.length)
|
|
224
|
+
snippet = snippet + '...';
|
|
225
|
+
return snippet.replace(/\n/g, ' ');
|
|
226
|
+
}
|
|
227
|
+
getDoc(args) {
|
|
228
|
+
const path = String(args.path || '');
|
|
229
|
+
if (!path) {
|
|
230
|
+
return {
|
|
231
|
+
content: [{ type: 'text', text: 'Error: path is required' }],
|
|
232
|
+
isError: true,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
const doc = this.docsIndex.find(d => d.path === path || d.path.endsWith(path));
|
|
236
|
+
if (!doc) {
|
|
237
|
+
const suggestions = this.docsIndex
|
|
238
|
+
.filter(d => d.path.includes(path.split('/').pop() || ''))
|
|
239
|
+
.slice(0, 3)
|
|
240
|
+
.map(d => d.path);
|
|
241
|
+
return {
|
|
242
|
+
content: [{
|
|
243
|
+
type: 'text',
|
|
244
|
+
text: `Documentation not found: ${path}${suggestions.length ? `\n\nDid you mean:\n${suggestions.map(s => `- ${s}`).join('\n')}` : ''}`,
|
|
245
|
+
}],
|
|
246
|
+
isError: true,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
content: [{
|
|
251
|
+
type: 'text',
|
|
252
|
+
text: `# ${doc.title}\n\nPath: ${doc.path}\nCategory: ${doc.category}\n\n---\n\n${doc.content}`,
|
|
253
|
+
}],
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
handleRequest(req) {
|
|
257
|
+
const { method, params, id } = req;
|
|
258
|
+
try {
|
|
259
|
+
switch (method) {
|
|
260
|
+
case 'initialize': {
|
|
261
|
+
const response = {
|
|
262
|
+
protocolVersion: '2024-11-05',
|
|
263
|
+
capabilities: {
|
|
264
|
+
tools: { listChanged: false },
|
|
265
|
+
},
|
|
266
|
+
serverInfo: {
|
|
267
|
+
name: this.options.name,
|
|
268
|
+
version: this.options.version,
|
|
269
|
+
},
|
|
270
|
+
};
|
|
271
|
+
return { jsonrpc: '2.0', id: id, result: response };
|
|
272
|
+
}
|
|
273
|
+
case 'ping':
|
|
274
|
+
return { jsonrpc: '2.0', id: id, result: {} };
|
|
275
|
+
case 'tools/list': {
|
|
276
|
+
const response = { tools: this.getTools() };
|
|
277
|
+
return { jsonrpc: '2.0', id: id, result: response };
|
|
278
|
+
}
|
|
279
|
+
case 'tools/call': {
|
|
280
|
+
const { name, arguments: args } = params;
|
|
281
|
+
const result = this.handleToolCall(name, args || {});
|
|
282
|
+
return { jsonrpc: '2.0', id: id, result };
|
|
283
|
+
}
|
|
284
|
+
default:
|
|
285
|
+
return {
|
|
286
|
+
jsonrpc: '2.0',
|
|
287
|
+
id: id,
|
|
288
|
+
error: { code: -32601, message: `Method not found: ${method}` },
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
catch (err) {
|
|
293
|
+
return {
|
|
294
|
+
jsonrpc: '2.0',
|
|
295
|
+
id: id,
|
|
296
|
+
error: { code: -32603, message: String(err) },
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
async start() {
|
|
301
|
+
return new Promise((resolve) => {
|
|
302
|
+
this.server = createServer((req, res) => {
|
|
303
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
304
|
+
res.setHeader('Access-Control-Allow-Methods', 'POST, OPTIONS');
|
|
305
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
306
|
+
if (req.method === 'OPTIONS') {
|
|
307
|
+
res.writeHead(204);
|
|
308
|
+
res.end();
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (req.method !== 'POST') {
|
|
312
|
+
res.writeHead(405);
|
|
313
|
+
res.end('Method not allowed');
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
let body = '';
|
|
317
|
+
req.on('data', chunk => body += chunk);
|
|
318
|
+
req.on('end', () => {
|
|
319
|
+
try {
|
|
320
|
+
const request = JSON.parse(body);
|
|
321
|
+
if (this.options.debug) {
|
|
322
|
+
console.log('[MCP] Request:', JSON.stringify(request, null, 2));
|
|
323
|
+
}
|
|
324
|
+
const response = this.handleRequest(request);
|
|
325
|
+
if (this.options.debug) {
|
|
326
|
+
console.log('[MCP] Response:', JSON.stringify(response, null, 2));
|
|
327
|
+
}
|
|
328
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
329
|
+
res.end(JSON.stringify(response));
|
|
330
|
+
}
|
|
331
|
+
catch (err) {
|
|
332
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
333
|
+
res.end(JSON.stringify({
|
|
334
|
+
jsonrpc: '2.0',
|
|
335
|
+
id: null,
|
|
336
|
+
error: { code: -32700, message: 'Parse error' },
|
|
337
|
+
}));
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
this.server.listen(this.options.port, () => {
|
|
342
|
+
if (this.options.debug) {
|
|
343
|
+
console.log(`[MCP] Server listening on http://localhost:${this.options.port}`);
|
|
344
|
+
console.log(`[MCP] Docs path: ${this.options.docsPath}`);
|
|
345
|
+
console.log(`[MCP] Indexed ${this.docsIndex.length} files`);
|
|
346
|
+
}
|
|
347
|
+
resolve();
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
async stop() {
|
|
352
|
+
return new Promise((resolve) => {
|
|
353
|
+
if (this.server) {
|
|
354
|
+
this.server.close(() => resolve());
|
|
355
|
+
}
|
|
356
|
+
else {
|
|
357
|
+
resolve();
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
getPort() {
|
|
362
|
+
return this.options.port;
|
|
363
|
+
}
|
|
364
|
+
getDocsCount() {
|
|
365
|
+
return this.docsIndex.length;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
export function createMCPServer(options) {
|
|
369
|
+
return new MCPServer(options);
|
|
370
|
+
}
|
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Plugin } from '../types/index.js';
|
|
2
|
-
export interface RateLimitOptions {
|
|
3
|
-
concurrency?: number;
|
|
4
|
-
requestsPerInterval?: number;
|
|
5
|
-
interval?: number;
|
|
6
|
-
}
|
|
7
|
-
export declare function rateLimit(options: RateLimitOptions): Plugin;
|
|
8
|
-
//# sourceMappingURL=rate-limit.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/plugins/rate-limit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAOvD,MAAM,WAAW,gBAAgB;IAK/B,WAAW,CAAC,EAAE,MAAM,CAAC;IAIrB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAI7B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAiBD,wBAAgB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,MAAM,CAqE3D"}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
export function rateLimit(options) {
|
|
2
|
-
const concurrency = options.concurrency || Infinity;
|
|
3
|
-
const limit = options.requestsPerInterval || Infinity;
|
|
4
|
-
const interval = options.interval || 1000;
|
|
5
|
-
let activeCount = 0;
|
|
6
|
-
const queue = [];
|
|
7
|
-
let tokens = limit;
|
|
8
|
-
let lastRefill = Date.now();
|
|
9
|
-
const refillTokens = () => {
|
|
10
|
-
const now = Date.now();
|
|
11
|
-
const timePassed = now - lastRefill;
|
|
12
|
-
if (timePassed >= interval) {
|
|
13
|
-
const intervalsPassed = Math.floor(timePassed / interval);
|
|
14
|
-
tokens = Math.min(limit, tokens + (intervalsPassed * limit));
|
|
15
|
-
lastRefill = now;
|
|
16
|
-
}
|
|
17
|
-
};
|
|
18
|
-
const processQueue = () => {
|
|
19
|
-
if (queue.length === 0)
|
|
20
|
-
return;
|
|
21
|
-
refillTokens();
|
|
22
|
-
while (queue.length > 0 && activeCount < concurrency && tokens > 0) {
|
|
23
|
-
const next = queue.shift();
|
|
24
|
-
if (next) {
|
|
25
|
-
activeCount++;
|
|
26
|
-
tokens--;
|
|
27
|
-
next();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (queue.length > 0 && tokens <= 0 && limit !== Infinity) {
|
|
31
|
-
const timeToWait = interval - (Date.now() - lastRefill);
|
|
32
|
-
setTimeout(processQueue, Math.max(0, timeToWait));
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const rateLimitMiddleware = (req, next) => {
|
|
36
|
-
return new Promise((resolve, reject) => {
|
|
37
|
-
const run = async () => {
|
|
38
|
-
try {
|
|
39
|
-
const res = await next(req);
|
|
40
|
-
resolve(res);
|
|
41
|
-
}
|
|
42
|
-
catch (err) {
|
|
43
|
-
reject(err);
|
|
44
|
-
}
|
|
45
|
-
finally {
|
|
46
|
-
activeCount--;
|
|
47
|
-
processQueue();
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
queue.push(run);
|
|
51
|
-
processQueue();
|
|
52
|
-
});
|
|
53
|
-
};
|
|
54
|
-
return (client) => {
|
|
55
|
-
client.use(rateLimitMiddleware);
|
|
56
|
-
};
|
|
57
|
-
}
|
package/dist/utils/logger.d.ts
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { ReckerRequest, ReckerResponse, Timings } from '../types/index.js';
|
|
2
|
-
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'none';
|
|
3
|
-
export interface LoggerOptions {
|
|
4
|
-
level?: LogLevel;
|
|
5
|
-
prefix?: string;
|
|
6
|
-
timestamp?: boolean;
|
|
7
|
-
colors?: boolean;
|
|
8
|
-
}
|
|
9
|
-
export declare class Logger {
|
|
10
|
-
private level;
|
|
11
|
-
private prefix;
|
|
12
|
-
private useTimestamp;
|
|
13
|
-
private useColors;
|
|
14
|
-
constructor(options?: LoggerOptions);
|
|
15
|
-
private detectLogLevel;
|
|
16
|
-
private supportsColors;
|
|
17
|
-
private colorize;
|
|
18
|
-
private formatTimestamp;
|
|
19
|
-
private shouldLog;
|
|
20
|
-
private log;
|
|
21
|
-
debug(message: string): void;
|
|
22
|
-
info(message: string): void;
|
|
23
|
-
warn(message: string): void;
|
|
24
|
-
error(message: string): void;
|
|
25
|
-
logRequest(req: ReckerRequest): void;
|
|
26
|
-
logResponse(req: ReckerRequest, res: ReckerResponse, startTime: number): void;
|
|
27
|
-
logTimings(timings: Timings): void;
|
|
28
|
-
logError(req: ReckerRequest, error: Error): void;
|
|
29
|
-
private formatBytes;
|
|
30
|
-
}
|
|
31
|
-
export declare function getLogger(): Logger;
|
|
32
|
-
export declare function setLogger(logger: Logger): void;
|
|
33
|
-
//# sourceMappingURL=logger.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../../src/utils/logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAE3E,MAAM,MAAM,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEpE,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,QAAQ,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AA2BD,qBAAa,MAAM;IACjB,OAAO,CAAC,KAAK,CAAW;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAU;IAC9B,OAAO,CAAC,SAAS,CAAU;gBAEf,OAAO,GAAE,aAAkB;IAOvC,OAAO,CAAC,cAAc;IAQtB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,QAAQ;IAKhB,OAAO,CAAC,eAAe;IAOvB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,GAAG;IAQX,KAAK,CAAC,OAAO,EAAE,MAAM;IAIrB,IAAI,CAAC,OAAO,EAAE,MAAM;IAIpB,IAAI,CAAC,OAAO,EAAE,MAAM;IAIpB,KAAK,CAAC,OAAO,EAAE,MAAM;IAOrB,UAAU,CAAC,GAAG,EAAE,aAAa;IAmB7B,WAAW,CAAC,GAAG,EAAE,aAAa,EAAE,GAAG,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM;IA4BtE,UAAU,CAAC,OAAO,EAAE,OAAO;IAoB3B,QAAQ,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,KAAK;IAoBzC,OAAO,CAAC,WAAW;CAOpB;AAKD,wBAAgB,SAAS,IAAI,MAAM,CAKlC;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,MAAM,QAEvC"}
|
package/dist/utils/logger.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
const colors = {
|
|
2
|
-
reset: '\x1b[0m',
|
|
3
|
-
bright: '\x1b[1m',
|
|
4
|
-
dim: '\x1b[2m',
|
|
5
|
-
red: '\x1b[31m',
|
|
6
|
-
green: '\x1b[32m',
|
|
7
|
-
yellow: '\x1b[33m',
|
|
8
|
-
blue: '\x1b[34m',
|
|
9
|
-
magenta: '\x1b[35m',
|
|
10
|
-
cyan: '\x1b[36m',
|
|
11
|
-
white: '\x1b[37m',
|
|
12
|
-
gray: '\x1b[90m',
|
|
13
|
-
};
|
|
14
|
-
const levels = {
|
|
15
|
-
debug: 0,
|
|
16
|
-
info: 1,
|
|
17
|
-
warn: 2,
|
|
18
|
-
error: 3,
|
|
19
|
-
none: 999,
|
|
20
|
-
};
|
|
21
|
-
export class Logger {
|
|
22
|
-
level;
|
|
23
|
-
prefix;
|
|
24
|
-
useTimestamp;
|
|
25
|
-
useColors;
|
|
26
|
-
constructor(options = {}) {
|
|
27
|
-
this.level = options.level || this.detectLogLevel();
|
|
28
|
-
this.prefix = options.prefix || 'recker';
|
|
29
|
-
this.useTimestamp = options.timestamp !== false;
|
|
30
|
-
this.useColors = options.colors !== false && this.supportsColors();
|
|
31
|
-
}
|
|
32
|
-
detectLogLevel() {
|
|
33
|
-
const env = process.env.DEBUG || '';
|
|
34
|
-
if (env === '*' || env.includes('recker') || env.includes('*')) {
|
|
35
|
-
return 'debug';
|
|
36
|
-
}
|
|
37
|
-
return 'none';
|
|
38
|
-
}
|
|
39
|
-
supportsColors() {
|
|
40
|
-
return (process.stdout.isTTY &&
|
|
41
|
-
!process.env.NO_COLOR &&
|
|
42
|
-
process.env.TERM !== 'dumb');
|
|
43
|
-
}
|
|
44
|
-
colorize(text, color) {
|
|
45
|
-
if (!this.useColors)
|
|
46
|
-
return text;
|
|
47
|
-
return `${colors[color]}${text}${colors.reset}`;
|
|
48
|
-
}
|
|
49
|
-
formatTimestamp() {
|
|
50
|
-
if (!this.useTimestamp)
|
|
51
|
-
return '';
|
|
52
|
-
const now = new Date();
|
|
53
|
-
const time = now.toTimeString().split(' ')[0];
|
|
54
|
-
return this.colorize(`[${time}]`, 'gray') + ' ';
|
|
55
|
-
}
|
|
56
|
-
shouldLog(level) {
|
|
57
|
-
return levels[level] >= levels[this.level];
|
|
58
|
-
}
|
|
59
|
-
log(level, message) {
|
|
60
|
-
if (!this.shouldLog(level))
|
|
61
|
-
return;
|
|
62
|
-
const timestamp = this.formatTimestamp();
|
|
63
|
-
const prefix = this.colorize(`[${this.prefix}]`, 'cyan');
|
|
64
|
-
console.log(`${timestamp}${prefix} ${message}`);
|
|
65
|
-
}
|
|
66
|
-
debug(message) {
|
|
67
|
-
this.log('debug', message);
|
|
68
|
-
}
|
|
69
|
-
info(message) {
|
|
70
|
-
this.log('info', message);
|
|
71
|
-
}
|
|
72
|
-
warn(message) {
|
|
73
|
-
this.log('warn', this.colorize(message, 'yellow'));
|
|
74
|
-
}
|
|
75
|
-
error(message) {
|
|
76
|
-
this.log('error', this.colorize(message, 'red'));
|
|
77
|
-
}
|
|
78
|
-
logRequest(req) {
|
|
79
|
-
if (!this.shouldLog('debug'))
|
|
80
|
-
return;
|
|
81
|
-
const method = this.colorize(req.method, 'blue');
|
|
82
|
-
const url = this.colorize(req.url, 'bright');
|
|
83
|
-
this.debug(`${this.colorize('→', 'green')} ${method} ${url}`);
|
|
84
|
-
if (req.headers && Array.from(req.headers.keys()).length > 0) {
|
|
85
|
-
const headerStr = Array.from(req.headers.entries())
|
|
86
|
-
.map(([k, v]) => ` ${this.colorize(k, 'gray')}: ${v}`)
|
|
87
|
-
.join('\n');
|
|
88
|
-
console.log(headerStr);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
logResponse(req, res, startTime) {
|
|
92
|
-
if (!this.shouldLog('debug'))
|
|
93
|
-
return;
|
|
94
|
-
const duration = Date.now() - startTime;
|
|
95
|
-
const statusColor = res.ok ? 'green' : 'red';
|
|
96
|
-
const status = this.colorize(String(res.status), statusColor);
|
|
97
|
-
const method = this.colorize(req.method, 'gray');
|
|
98
|
-
this.debug(`${this.colorize('←', 'green')} ${status} ${method} ${req.url} ${this.colorize(`(${duration}ms)`, 'gray')}`);
|
|
99
|
-
if (res.timings) {
|
|
100
|
-
this.logTimings(res.timings);
|
|
101
|
-
}
|
|
102
|
-
const contentLength = res.headers.get('content-length');
|
|
103
|
-
if (contentLength) {
|
|
104
|
-
const size = this.formatBytes(parseInt(contentLength, 10));
|
|
105
|
-
console.log(` ${this.colorize('Size:', 'gray')} ${size}`);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
logTimings(timings) {
|
|
109
|
-
if (!this.shouldLog('debug'))
|
|
110
|
-
return;
|
|
111
|
-
const parts = [];
|
|
112
|
-
if (timings.dns)
|
|
113
|
-
parts.push(`DNS: ${timings.dns.toFixed(0)}ms`);
|
|
114
|
-
if (timings.tcp)
|
|
115
|
-
parts.push(`TCP: ${timings.tcp.toFixed(0)}ms`);
|
|
116
|
-
if (timings.tls)
|
|
117
|
-
parts.push(`TLS: ${timings.tls.toFixed(0)}ms`);
|
|
118
|
-
if (timings.firstByte)
|
|
119
|
-
parts.push(`TTFB: ${timings.firstByte.toFixed(0)}ms`);
|
|
120
|
-
if (timings.total)
|
|
121
|
-
parts.push(`Total: ${timings.total.toFixed(0)}ms`);
|
|
122
|
-
if (parts.length > 0) {
|
|
123
|
-
const timelinesStr = parts.join(', ');
|
|
124
|
-
console.log(` ${this.colorize('├─', 'gray')} ${timelinesStr}`);
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
logError(req, error) {
|
|
128
|
-
if (!this.shouldLog('error'))
|
|
129
|
-
return;
|
|
130
|
-
const method = this.colorize(req.method, 'gray');
|
|
131
|
-
this.error(`${this.colorize('✖', 'red')} ${method} ${req.url}`);
|
|
132
|
-
console.log(` ${this.colorize('Error:', 'red')} ${error.message}`);
|
|
133
|
-
if (error.stack && this.shouldLog('debug')) {
|
|
134
|
-
const stack = error.stack
|
|
135
|
-
.split('\n')
|
|
136
|
-
.slice(1, 4)
|
|
137
|
-
.map((line) => ` ${this.colorize('│', 'gray')} ${line.trim()}`)
|
|
138
|
-
.join('\n');
|
|
139
|
-
console.log(stack);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
formatBytes(bytes) {
|
|
143
|
-
if (bytes === 0)
|
|
144
|
-
return '0 B';
|
|
145
|
-
const k = 1024;
|
|
146
|
-
const sizes = ['B', 'KB', 'MB', 'GB'];
|
|
147
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
148
|
-
return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`;
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
let globalLogger = null;
|
|
152
|
-
export function getLogger() {
|
|
153
|
-
if (!globalLogger) {
|
|
154
|
-
globalLogger = new Logger();
|
|
155
|
-
}
|
|
156
|
-
return globalLogger;
|
|
157
|
-
}
|
|
158
|
-
export function setLogger(logger) {
|
|
159
|
-
globalLogger = logger;
|
|
160
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
export declare enum StatusCode {
|
|
2
|
-
CONTINUE = 100,
|
|
3
|
-
SWITCHING_PROTOCOLS = 101,
|
|
4
|
-
PROCESSING = 102,
|
|
5
|
-
EARLY_HINTS = 103,
|
|
6
|
-
OK = 200,
|
|
7
|
-
CREATED = 201,
|
|
8
|
-
ACCEPTED = 202,
|
|
9
|
-
NON_AUTHORITATIVE_INFORMATION = 203,
|
|
10
|
-
NO_CONTENT = 204,
|
|
11
|
-
RESET_CONTENT = 205,
|
|
12
|
-
PARTIAL_CONTENT = 206,
|
|
13
|
-
MULTI_STATUS = 207,
|
|
14
|
-
ALREADY_REPORTED = 208,
|
|
15
|
-
IM_USED = 226,
|
|
16
|
-
MULTIPLE_CHOICES = 300,
|
|
17
|
-
MOVED_PERMANENTLY = 301,
|
|
18
|
-
FOUND = 302,
|
|
19
|
-
SEE_OTHER = 303,
|
|
20
|
-
NOT_MODIFIED = 304,
|
|
21
|
-
USE_PROXY = 305,
|
|
22
|
-
TEMPORARY_REDIRECT = 307,
|
|
23
|
-
PERMANENT_REDIRECT = 308,
|
|
24
|
-
BAD_REQUEST = 400,
|
|
25
|
-
UNAUTHORIZED = 401,
|
|
26
|
-
PAYMENT_REQUIRED = 402,
|
|
27
|
-
FORBIDDEN = 403,
|
|
28
|
-
NOT_FOUND = 404,
|
|
29
|
-
METHOD_NOT_ALLOWED = 405,
|
|
30
|
-
NOT_ACCEPTABLE = 406,
|
|
31
|
-
PROXY_AUTHENTICATION_REQUIRED = 407,
|
|
32
|
-
REQUEST_TIMEOUT = 408,
|
|
33
|
-
CONFLICT = 409,
|
|
34
|
-
GONE = 410,
|
|
35
|
-
LENGTH_REQUIRED = 411,
|
|
36
|
-
PRECONDITION_FAILED = 412,
|
|
37
|
-
PAYLOAD_TOO_LARGE = 413,
|
|
38
|
-
URI_TOO_LONG = 414,
|
|
39
|
-
UNSUPPORTED_MEDIA_TYPE = 415,
|
|
40
|
-
RANGE_NOT_SATISFIABLE = 416,
|
|
41
|
-
EXPECTATION_FAILED = 417,
|
|
42
|
-
IM_A_TEAPOT = 418,
|
|
43
|
-
MISDIRECTED_REQUEST = 421,
|
|
44
|
-
UNPROCESSABLE_ENTITY = 422,
|
|
45
|
-
LOCKED = 423,
|
|
46
|
-
FAILED_DEPENDENCY = 424,
|
|
47
|
-
TOO_EARLY = 425,
|
|
48
|
-
UPGRADE_REQUIRED = 426,
|
|
49
|
-
PRECONDITION_REQUIRED = 428,
|
|
50
|
-
TOO_MANY_REQUESTS = 429,
|
|
51
|
-
REQUEST_HEADER_FIELDS_TOO_LARGE = 431,
|
|
52
|
-
UNAVAILABLE_FOR_LEGAL_REASONS = 451,
|
|
53
|
-
INTERNAL_SERVER_ERROR = 500,
|
|
54
|
-
NOT_IMPLEMENTED = 501,
|
|
55
|
-
BAD_GATEWAY = 502,
|
|
56
|
-
SERVICE_UNAVAILABLE = 503,
|
|
57
|
-
GATEWAY_TIMEOUT = 504,
|
|
58
|
-
HTTP_VERSION_NOT_SUPPORTED = 505,
|
|
59
|
-
VARIANT_ALSO_NEGOTIATES = 506,
|
|
60
|
-
INSUFFICIENT_STORAGE = 507,
|
|
61
|
-
LOOP_DETECTED = 508,
|
|
62
|
-
NOT_EXTENDED = 510,
|
|
63
|
-
NETWORK_AUTHENTICATION_REQUIRED = 511
|
|
64
|
-
}
|
|
65
|
-
export declare const STATUS_TEXT: Record<number, string>;
|
|
66
|
-
export declare const status: {
|
|
67
|
-
isInformational(code: number): boolean;
|
|
68
|
-
isSuccess(code: number): boolean;
|
|
69
|
-
isRedirect(code: number): boolean;
|
|
70
|
-
isClientError(code: number): boolean;
|
|
71
|
-
isServerError(code: number): boolean;
|
|
72
|
-
isError(code: number): boolean;
|
|
73
|
-
isOk(code: number): boolean;
|
|
74
|
-
getText(code: number): string | undefined;
|
|
75
|
-
getCategory(code: number): string;
|
|
76
|
-
};
|
|
77
|
-
export declare const statusGroups: {
|
|
78
|
-
success: readonly [StatusCode.OK, StatusCode.CREATED, StatusCode.ACCEPTED, StatusCode.NO_CONTENT];
|
|
79
|
-
redirect: readonly [StatusCode.MOVED_PERMANENTLY, StatusCode.FOUND, StatusCode.SEE_OTHER, StatusCode.TEMPORARY_REDIRECT, StatusCode.PERMANENT_REDIRECT];
|
|
80
|
-
clientError: readonly [StatusCode.BAD_REQUEST, StatusCode.UNAUTHORIZED, StatusCode.FORBIDDEN, StatusCode.NOT_FOUND, StatusCode.METHOD_NOT_ALLOWED, StatusCode.CONFLICT, StatusCode.TOO_MANY_REQUESTS];
|
|
81
|
-
serverError: readonly [StatusCode.INTERNAL_SERVER_ERROR, StatusCode.BAD_GATEWAY, StatusCode.SERVICE_UNAVAILABLE, StatusCode.GATEWAY_TIMEOUT];
|
|
82
|
-
retryable: readonly [StatusCode.TOO_MANY_REQUESTS, StatusCode.BAD_GATEWAY, StatusCode.SERVICE_UNAVAILABLE, StatusCode.GATEWAY_TIMEOUT];
|
|
83
|
-
};
|
|
84
|
-
//# sourceMappingURL=status-codes.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"status-codes.d.ts","sourceRoot":"","sources":["../../src/utils/status-codes.ts"],"names":[],"mappings":"AAUA,oBAAY,UAAU;IAEpB,QAAQ,MAAM;IACd,mBAAmB,MAAM;IACzB,UAAU,MAAM;IAChB,WAAW,MAAM;IAGjB,EAAE,MAAM;IACR,OAAO,MAAM;IACb,QAAQ,MAAM;IACd,6BAA6B,MAAM;IACnC,UAAU,MAAM;IAChB,aAAa,MAAM;IACnB,eAAe,MAAM;IACrB,YAAY,MAAM;IAClB,gBAAgB,MAAM;IACtB,OAAO,MAAM;IAGb,gBAAgB,MAAM;IACtB,iBAAiB,MAAM;IACvB,KAAK,MAAM;IACX,SAAS,MAAM;IACf,YAAY,MAAM;IAClB,SAAS,MAAM;IACf,kBAAkB,MAAM;IACxB,kBAAkB,MAAM;IAGxB,WAAW,MAAM;IACjB,YAAY,MAAM;IAClB,gBAAgB,MAAM;IACtB,SAAS,MAAM;IACf,SAAS,MAAM;IACf,kBAAkB,MAAM;IACxB,cAAc,MAAM;IACpB,6BAA6B,MAAM;IACnC,eAAe,MAAM;IACrB,QAAQ,MAAM;IACd,IAAI,MAAM;IACV,eAAe,MAAM;IACrB,mBAAmB,MAAM;IACzB,iBAAiB,MAAM;IACvB,YAAY,MAAM;IAClB,sBAAsB,MAAM;IAC5B,qBAAqB,MAAM;IAC3B,kBAAkB,MAAM;IACxB,WAAW,MAAM;IACjB,mBAAmB,MAAM;IACzB,oBAAoB,MAAM;IAC1B,MAAM,MAAM;IACZ,iBAAiB,MAAM;IACvB,SAAS,MAAM;IACf,gBAAgB,MAAM;IACtB,qBAAqB,MAAM;IAC3B,iBAAiB,MAAM;IACvB,+BAA+B,MAAM;IACrC,6BAA6B,MAAM;IAGnC,qBAAqB,MAAM;IAC3B,eAAe,MAAM;IACrB,WAAW,MAAM;IACjB,mBAAmB,MAAM;IACzB,eAAe,MAAM;IACrB,0BAA0B,MAAM;IAChC,uBAAuB,MAAM;IAC7B,oBAAoB,MAAM;IAC1B,aAAa,MAAM;IACnB,YAAY,MAAM;IAClB,+BAA+B,MAAM;CACtC;AAKD,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAwE9C,CAAC;AAKF,eAAO,MAAM,MAAM;0BAIK,MAAM,GAAG,OAAO;oBAOtB,MAAM,GAAG,OAAO;qBAOf,MAAM,GAAG,OAAO;wBAOb,MAAM,GAAG,OAAO;wBAOhB,MAAM,GAAG,OAAO;kBAOtB,MAAM,GAAG,OAAO;eAQnB,MAAM,GAAG,OAAO;kBAOb,MAAM,GAAG,MAAM,GAAG,SAAS;sBAOvB,MAAM,GAAG,MAAM;CAQlC,CAAC;AAKF,eAAO,MAAM,YAAY;;;;;;CAsDxB,CAAC"}
|
|
@@ -1,204 +0,0 @@
|
|
|
1
|
-
export var StatusCode;
|
|
2
|
-
(function (StatusCode) {
|
|
3
|
-
StatusCode[StatusCode["CONTINUE"] = 100] = "CONTINUE";
|
|
4
|
-
StatusCode[StatusCode["SWITCHING_PROTOCOLS"] = 101] = "SWITCHING_PROTOCOLS";
|
|
5
|
-
StatusCode[StatusCode["PROCESSING"] = 102] = "PROCESSING";
|
|
6
|
-
StatusCode[StatusCode["EARLY_HINTS"] = 103] = "EARLY_HINTS";
|
|
7
|
-
StatusCode[StatusCode["OK"] = 200] = "OK";
|
|
8
|
-
StatusCode[StatusCode["CREATED"] = 201] = "CREATED";
|
|
9
|
-
StatusCode[StatusCode["ACCEPTED"] = 202] = "ACCEPTED";
|
|
10
|
-
StatusCode[StatusCode["NON_AUTHORITATIVE_INFORMATION"] = 203] = "NON_AUTHORITATIVE_INFORMATION";
|
|
11
|
-
StatusCode[StatusCode["NO_CONTENT"] = 204] = "NO_CONTENT";
|
|
12
|
-
StatusCode[StatusCode["RESET_CONTENT"] = 205] = "RESET_CONTENT";
|
|
13
|
-
StatusCode[StatusCode["PARTIAL_CONTENT"] = 206] = "PARTIAL_CONTENT";
|
|
14
|
-
StatusCode[StatusCode["MULTI_STATUS"] = 207] = "MULTI_STATUS";
|
|
15
|
-
StatusCode[StatusCode["ALREADY_REPORTED"] = 208] = "ALREADY_REPORTED";
|
|
16
|
-
StatusCode[StatusCode["IM_USED"] = 226] = "IM_USED";
|
|
17
|
-
StatusCode[StatusCode["MULTIPLE_CHOICES"] = 300] = "MULTIPLE_CHOICES";
|
|
18
|
-
StatusCode[StatusCode["MOVED_PERMANENTLY"] = 301] = "MOVED_PERMANENTLY";
|
|
19
|
-
StatusCode[StatusCode["FOUND"] = 302] = "FOUND";
|
|
20
|
-
StatusCode[StatusCode["SEE_OTHER"] = 303] = "SEE_OTHER";
|
|
21
|
-
StatusCode[StatusCode["NOT_MODIFIED"] = 304] = "NOT_MODIFIED";
|
|
22
|
-
StatusCode[StatusCode["USE_PROXY"] = 305] = "USE_PROXY";
|
|
23
|
-
StatusCode[StatusCode["TEMPORARY_REDIRECT"] = 307] = "TEMPORARY_REDIRECT";
|
|
24
|
-
StatusCode[StatusCode["PERMANENT_REDIRECT"] = 308] = "PERMANENT_REDIRECT";
|
|
25
|
-
StatusCode[StatusCode["BAD_REQUEST"] = 400] = "BAD_REQUEST";
|
|
26
|
-
StatusCode[StatusCode["UNAUTHORIZED"] = 401] = "UNAUTHORIZED";
|
|
27
|
-
StatusCode[StatusCode["PAYMENT_REQUIRED"] = 402] = "PAYMENT_REQUIRED";
|
|
28
|
-
StatusCode[StatusCode["FORBIDDEN"] = 403] = "FORBIDDEN";
|
|
29
|
-
StatusCode[StatusCode["NOT_FOUND"] = 404] = "NOT_FOUND";
|
|
30
|
-
StatusCode[StatusCode["METHOD_NOT_ALLOWED"] = 405] = "METHOD_NOT_ALLOWED";
|
|
31
|
-
StatusCode[StatusCode["NOT_ACCEPTABLE"] = 406] = "NOT_ACCEPTABLE";
|
|
32
|
-
StatusCode[StatusCode["PROXY_AUTHENTICATION_REQUIRED"] = 407] = "PROXY_AUTHENTICATION_REQUIRED";
|
|
33
|
-
StatusCode[StatusCode["REQUEST_TIMEOUT"] = 408] = "REQUEST_TIMEOUT";
|
|
34
|
-
StatusCode[StatusCode["CONFLICT"] = 409] = "CONFLICT";
|
|
35
|
-
StatusCode[StatusCode["GONE"] = 410] = "GONE";
|
|
36
|
-
StatusCode[StatusCode["LENGTH_REQUIRED"] = 411] = "LENGTH_REQUIRED";
|
|
37
|
-
StatusCode[StatusCode["PRECONDITION_FAILED"] = 412] = "PRECONDITION_FAILED";
|
|
38
|
-
StatusCode[StatusCode["PAYLOAD_TOO_LARGE"] = 413] = "PAYLOAD_TOO_LARGE";
|
|
39
|
-
StatusCode[StatusCode["URI_TOO_LONG"] = 414] = "URI_TOO_LONG";
|
|
40
|
-
StatusCode[StatusCode["UNSUPPORTED_MEDIA_TYPE"] = 415] = "UNSUPPORTED_MEDIA_TYPE";
|
|
41
|
-
StatusCode[StatusCode["RANGE_NOT_SATISFIABLE"] = 416] = "RANGE_NOT_SATISFIABLE";
|
|
42
|
-
StatusCode[StatusCode["EXPECTATION_FAILED"] = 417] = "EXPECTATION_FAILED";
|
|
43
|
-
StatusCode[StatusCode["IM_A_TEAPOT"] = 418] = "IM_A_TEAPOT";
|
|
44
|
-
StatusCode[StatusCode["MISDIRECTED_REQUEST"] = 421] = "MISDIRECTED_REQUEST";
|
|
45
|
-
StatusCode[StatusCode["UNPROCESSABLE_ENTITY"] = 422] = "UNPROCESSABLE_ENTITY";
|
|
46
|
-
StatusCode[StatusCode["LOCKED"] = 423] = "LOCKED";
|
|
47
|
-
StatusCode[StatusCode["FAILED_DEPENDENCY"] = 424] = "FAILED_DEPENDENCY";
|
|
48
|
-
StatusCode[StatusCode["TOO_EARLY"] = 425] = "TOO_EARLY";
|
|
49
|
-
StatusCode[StatusCode["UPGRADE_REQUIRED"] = 426] = "UPGRADE_REQUIRED";
|
|
50
|
-
StatusCode[StatusCode["PRECONDITION_REQUIRED"] = 428] = "PRECONDITION_REQUIRED";
|
|
51
|
-
StatusCode[StatusCode["TOO_MANY_REQUESTS"] = 429] = "TOO_MANY_REQUESTS";
|
|
52
|
-
StatusCode[StatusCode["REQUEST_HEADER_FIELDS_TOO_LARGE"] = 431] = "REQUEST_HEADER_FIELDS_TOO_LARGE";
|
|
53
|
-
StatusCode[StatusCode["UNAVAILABLE_FOR_LEGAL_REASONS"] = 451] = "UNAVAILABLE_FOR_LEGAL_REASONS";
|
|
54
|
-
StatusCode[StatusCode["INTERNAL_SERVER_ERROR"] = 500] = "INTERNAL_SERVER_ERROR";
|
|
55
|
-
StatusCode[StatusCode["NOT_IMPLEMENTED"] = 501] = "NOT_IMPLEMENTED";
|
|
56
|
-
StatusCode[StatusCode["BAD_GATEWAY"] = 502] = "BAD_GATEWAY";
|
|
57
|
-
StatusCode[StatusCode["SERVICE_UNAVAILABLE"] = 503] = "SERVICE_UNAVAILABLE";
|
|
58
|
-
StatusCode[StatusCode["GATEWAY_TIMEOUT"] = 504] = "GATEWAY_TIMEOUT";
|
|
59
|
-
StatusCode[StatusCode["HTTP_VERSION_NOT_SUPPORTED"] = 505] = "HTTP_VERSION_NOT_SUPPORTED";
|
|
60
|
-
StatusCode[StatusCode["VARIANT_ALSO_NEGOTIATES"] = 506] = "VARIANT_ALSO_NEGOTIATES";
|
|
61
|
-
StatusCode[StatusCode["INSUFFICIENT_STORAGE"] = 507] = "INSUFFICIENT_STORAGE";
|
|
62
|
-
StatusCode[StatusCode["LOOP_DETECTED"] = 508] = "LOOP_DETECTED";
|
|
63
|
-
StatusCode[StatusCode["NOT_EXTENDED"] = 510] = "NOT_EXTENDED";
|
|
64
|
-
StatusCode[StatusCode["NETWORK_AUTHENTICATION_REQUIRED"] = 511] = "NETWORK_AUTHENTICATION_REQUIRED";
|
|
65
|
-
})(StatusCode || (StatusCode = {}));
|
|
66
|
-
export const STATUS_TEXT = {
|
|
67
|
-
[StatusCode.CONTINUE]: 'Continue',
|
|
68
|
-
[StatusCode.SWITCHING_PROTOCOLS]: 'Switching Protocols',
|
|
69
|
-
[StatusCode.PROCESSING]: 'Processing',
|
|
70
|
-
[StatusCode.EARLY_HINTS]: 'Early Hints',
|
|
71
|
-
[StatusCode.OK]: 'OK',
|
|
72
|
-
[StatusCode.CREATED]: 'Created',
|
|
73
|
-
[StatusCode.ACCEPTED]: 'Accepted',
|
|
74
|
-
[StatusCode.NON_AUTHORITATIVE_INFORMATION]: 'Non-Authoritative Information',
|
|
75
|
-
[StatusCode.NO_CONTENT]: 'No Content',
|
|
76
|
-
[StatusCode.RESET_CONTENT]: 'Reset Content',
|
|
77
|
-
[StatusCode.PARTIAL_CONTENT]: 'Partial Content',
|
|
78
|
-
[StatusCode.MULTI_STATUS]: 'Multi-Status',
|
|
79
|
-
[StatusCode.ALREADY_REPORTED]: 'Already Reported',
|
|
80
|
-
[StatusCode.IM_USED]: 'IM Used',
|
|
81
|
-
[StatusCode.MULTIPLE_CHOICES]: 'Multiple Choices',
|
|
82
|
-
[StatusCode.MOVED_PERMANENTLY]: 'Moved Permanently',
|
|
83
|
-
[StatusCode.FOUND]: 'Found',
|
|
84
|
-
[StatusCode.SEE_OTHER]: 'See Other',
|
|
85
|
-
[StatusCode.NOT_MODIFIED]: 'Not Modified',
|
|
86
|
-
[StatusCode.USE_PROXY]: 'Use Proxy',
|
|
87
|
-
[StatusCode.TEMPORARY_REDIRECT]: 'Temporary Redirect',
|
|
88
|
-
[StatusCode.PERMANENT_REDIRECT]: 'Permanent Redirect',
|
|
89
|
-
[StatusCode.BAD_REQUEST]: 'Bad Request',
|
|
90
|
-
[StatusCode.UNAUTHORIZED]: 'Unauthorized',
|
|
91
|
-
[StatusCode.PAYMENT_REQUIRED]: 'Payment Required',
|
|
92
|
-
[StatusCode.FORBIDDEN]: 'Forbidden',
|
|
93
|
-
[StatusCode.NOT_FOUND]: 'Not Found',
|
|
94
|
-
[StatusCode.METHOD_NOT_ALLOWED]: 'Method Not Allowed',
|
|
95
|
-
[StatusCode.NOT_ACCEPTABLE]: 'Not Acceptable',
|
|
96
|
-
[StatusCode.PROXY_AUTHENTICATION_REQUIRED]: 'Proxy Authentication Required',
|
|
97
|
-
[StatusCode.REQUEST_TIMEOUT]: 'Request Timeout',
|
|
98
|
-
[StatusCode.CONFLICT]: 'Conflict',
|
|
99
|
-
[StatusCode.GONE]: 'Gone',
|
|
100
|
-
[StatusCode.LENGTH_REQUIRED]: 'Length Required',
|
|
101
|
-
[StatusCode.PRECONDITION_FAILED]: 'Precondition Failed',
|
|
102
|
-
[StatusCode.PAYLOAD_TOO_LARGE]: 'Payload Too Large',
|
|
103
|
-
[StatusCode.URI_TOO_LONG]: 'URI Too Long',
|
|
104
|
-
[StatusCode.UNSUPPORTED_MEDIA_TYPE]: 'Unsupported Media Type',
|
|
105
|
-
[StatusCode.RANGE_NOT_SATISFIABLE]: 'Range Not Satisfiable',
|
|
106
|
-
[StatusCode.EXPECTATION_FAILED]: 'Expectation Failed',
|
|
107
|
-
[StatusCode.IM_A_TEAPOT]: "I'm a teapot",
|
|
108
|
-
[StatusCode.MISDIRECTED_REQUEST]: 'Misdirected Request',
|
|
109
|
-
[StatusCode.UNPROCESSABLE_ENTITY]: 'Unprocessable Entity',
|
|
110
|
-
[StatusCode.LOCKED]: 'Locked',
|
|
111
|
-
[StatusCode.FAILED_DEPENDENCY]: 'Failed Dependency',
|
|
112
|
-
[StatusCode.TOO_EARLY]: 'Too Early',
|
|
113
|
-
[StatusCode.UPGRADE_REQUIRED]: 'Upgrade Required',
|
|
114
|
-
[StatusCode.PRECONDITION_REQUIRED]: 'Precondition Required',
|
|
115
|
-
[StatusCode.TOO_MANY_REQUESTS]: 'Too Many Requests',
|
|
116
|
-
[StatusCode.REQUEST_HEADER_FIELDS_TOO_LARGE]: 'Request Header Fields Too Large',
|
|
117
|
-
[StatusCode.UNAVAILABLE_FOR_LEGAL_REASONS]: 'Unavailable For Legal Reasons',
|
|
118
|
-
[StatusCode.INTERNAL_SERVER_ERROR]: 'Internal Server Error',
|
|
119
|
-
[StatusCode.NOT_IMPLEMENTED]: 'Not Implemented',
|
|
120
|
-
[StatusCode.BAD_GATEWAY]: 'Bad Gateway',
|
|
121
|
-
[StatusCode.SERVICE_UNAVAILABLE]: 'Service Unavailable',
|
|
122
|
-
[StatusCode.GATEWAY_TIMEOUT]: 'Gateway Timeout',
|
|
123
|
-
[StatusCode.HTTP_VERSION_NOT_SUPPORTED]: 'HTTP Version Not Supported',
|
|
124
|
-
[StatusCode.VARIANT_ALSO_NEGOTIATES]: 'Variant Also Negotiates',
|
|
125
|
-
[StatusCode.INSUFFICIENT_STORAGE]: 'Insufficient Storage',
|
|
126
|
-
[StatusCode.LOOP_DETECTED]: 'Loop Detected',
|
|
127
|
-
[StatusCode.NOT_EXTENDED]: 'Not Extended',
|
|
128
|
-
[StatusCode.NETWORK_AUTHENTICATION_REQUIRED]: 'Network Authentication Required',
|
|
129
|
-
};
|
|
130
|
-
export const status = {
|
|
131
|
-
isInformational(code) {
|
|
132
|
-
return code >= 100 && code < 200;
|
|
133
|
-
},
|
|
134
|
-
isSuccess(code) {
|
|
135
|
-
return code >= 200 && code < 300;
|
|
136
|
-
},
|
|
137
|
-
isRedirect(code) {
|
|
138
|
-
return code >= 300 && code < 400;
|
|
139
|
-
},
|
|
140
|
-
isClientError(code) {
|
|
141
|
-
return code >= 400 && code < 500;
|
|
142
|
-
},
|
|
143
|
-
isServerError(code) {
|
|
144
|
-
return code >= 500 && code < 600;
|
|
145
|
-
},
|
|
146
|
-
isError(code) {
|
|
147
|
-
return code >= 400 && code < 600;
|
|
148
|
-
},
|
|
149
|
-
isOk(code) {
|
|
150
|
-
return this.isSuccess(code);
|
|
151
|
-
},
|
|
152
|
-
getText(code) {
|
|
153
|
-
return STATUS_TEXT[code];
|
|
154
|
-
},
|
|
155
|
-
getCategory(code) {
|
|
156
|
-
if (this.isInformational(code))
|
|
157
|
-
return 'Informational';
|
|
158
|
-
if (this.isSuccess(code))
|
|
159
|
-
return 'Success';
|
|
160
|
-
if (this.isRedirect(code))
|
|
161
|
-
return 'Redirection';
|
|
162
|
-
if (this.isClientError(code))
|
|
163
|
-
return 'Client Error';
|
|
164
|
-
if (this.isServerError(code))
|
|
165
|
-
return 'Server Error';
|
|
166
|
-
return 'Unknown';
|
|
167
|
-
},
|
|
168
|
-
};
|
|
169
|
-
export const statusGroups = {
|
|
170
|
-
success: [
|
|
171
|
-
StatusCode.OK,
|
|
172
|
-
StatusCode.CREATED,
|
|
173
|
-
StatusCode.ACCEPTED,
|
|
174
|
-
StatusCode.NO_CONTENT,
|
|
175
|
-
],
|
|
176
|
-
redirect: [
|
|
177
|
-
StatusCode.MOVED_PERMANENTLY,
|
|
178
|
-
StatusCode.FOUND,
|
|
179
|
-
StatusCode.SEE_OTHER,
|
|
180
|
-
StatusCode.TEMPORARY_REDIRECT,
|
|
181
|
-
StatusCode.PERMANENT_REDIRECT,
|
|
182
|
-
],
|
|
183
|
-
clientError: [
|
|
184
|
-
StatusCode.BAD_REQUEST,
|
|
185
|
-
StatusCode.UNAUTHORIZED,
|
|
186
|
-
StatusCode.FORBIDDEN,
|
|
187
|
-
StatusCode.NOT_FOUND,
|
|
188
|
-
StatusCode.METHOD_NOT_ALLOWED,
|
|
189
|
-
StatusCode.CONFLICT,
|
|
190
|
-
StatusCode.TOO_MANY_REQUESTS,
|
|
191
|
-
],
|
|
192
|
-
serverError: [
|
|
193
|
-
StatusCode.INTERNAL_SERVER_ERROR,
|
|
194
|
-
StatusCode.BAD_GATEWAY,
|
|
195
|
-
StatusCode.SERVICE_UNAVAILABLE,
|
|
196
|
-
StatusCode.GATEWAY_TIMEOUT,
|
|
197
|
-
],
|
|
198
|
-
retryable: [
|
|
199
|
-
StatusCode.TOO_MANY_REQUESTS,
|
|
200
|
-
StatusCode.BAD_GATEWAY,
|
|
201
|
-
StatusCode.SERVICE_UNAVAILABLE,
|
|
202
|
-
StatusCode.GATEWAY_TIMEOUT,
|
|
203
|
-
],
|
|
204
|
-
};
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export interface TaskPoolOptions {
|
|
2
|
-
/**
|
|
3
|
-
* Max concurrent tasks allowed to execute at once.
|
|
4
|
-
* @default Infinity (no concurrency cap)
|
|
5
|
-
*/
|
|
6
|
-
concurrency?: number;
|
|
7
|
-
/**
|
|
8
|
-
* Requests allowed per interval window.
|
|
9
|
-
* When provided with `interval`, starts will be spaced to respect the cap.
|
|
10
|
-
*/
|
|
11
|
-
requestsPerInterval?: number;
|
|
12
|
-
/**
|
|
13
|
-
* Interval window length in milliseconds.
|
|
14
|
-
*/
|
|
15
|
-
interval?: number;
|
|
16
|
-
}
|
|
17
|
-
/**
|
|
18
|
-
* Lightweight TaskPool for rate limiting and concurrency control.
|
|
19
|
-
*
|
|
20
|
-
* - Limits concurrent executions.
|
|
21
|
-
* - Enforces a max start rate (`requestsPerInterval` / `interval`) via a sliding window.
|
|
22
|
-
* - Respects AbortSignal both while queued and while running.
|
|
23
|
-
*/
|
|
24
|
-
export declare class TaskPool {
|
|
25
|
-
private readonly concurrency;
|
|
26
|
-
private readonly requestsPerInterval?;
|
|
27
|
-
private readonly interval?;
|
|
28
|
-
private queue;
|
|
29
|
-
private active;
|
|
30
|
-
private windowStart;
|
|
31
|
-
private startedInWindow;
|
|
32
|
-
private waitingTimer?;
|
|
33
|
-
constructor(options?: TaskPoolOptions);
|
|
34
|
-
run<T>(fn: () => Promise<T>, signal?: AbortSignal): Promise<T>;
|
|
35
|
-
private _removeFromQueue;
|
|
36
|
-
private _canStart;
|
|
37
|
-
private _schedule;
|
|
38
|
-
}
|
package/dist/utils/task-pool.js
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Lightweight TaskPool for rate limiting and concurrency control.
|
|
3
|
-
*
|
|
4
|
-
* - Limits concurrent executions.
|
|
5
|
-
* - Enforces a max start rate (`requestsPerInterval` / `interval`) via a sliding window.
|
|
6
|
-
* - Respects AbortSignal both while queued and while running.
|
|
7
|
-
*/
|
|
8
|
-
export class TaskPool {
|
|
9
|
-
concurrency;
|
|
10
|
-
requestsPerInterval;
|
|
11
|
-
interval;
|
|
12
|
-
queue = [];
|
|
13
|
-
active = 0;
|
|
14
|
-
windowStart = 0;
|
|
15
|
-
startedInWindow = 0;
|
|
16
|
-
waitingTimer;
|
|
17
|
-
constructor(options = {}) {
|
|
18
|
-
this.concurrency = options.concurrency ?? Number.POSITIVE_INFINITY;
|
|
19
|
-
this.requestsPerInterval = options.requestsPerInterval;
|
|
20
|
-
this.interval = options.interval;
|
|
21
|
-
}
|
|
22
|
-
run(fn, signal) {
|
|
23
|
-
if (signal?.aborted) {
|
|
24
|
-
return Promise.reject(signal.reason ?? new Error('Task aborted before enqueue'));
|
|
25
|
-
}
|
|
26
|
-
return new Promise((resolve, reject) => {
|
|
27
|
-
const task = { fn, resolve, reject, signal };
|
|
28
|
-
if (signal) {
|
|
29
|
-
const onAbort = () => {
|
|
30
|
-
this._removeFromQueue(task);
|
|
31
|
-
reject(signal.reason ?? new Error('Task aborted while queued'));
|
|
32
|
-
this._schedule();
|
|
33
|
-
};
|
|
34
|
-
signal.addEventListener('abort', onAbort, { once: true });
|
|
35
|
-
task.abortCleanup = () => signal.removeEventListener('abort', onAbort);
|
|
36
|
-
}
|
|
37
|
-
this.queue.push(task);
|
|
38
|
-
this._schedule();
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
_removeFromQueue(task) {
|
|
42
|
-
const index = this.queue.indexOf(task);
|
|
43
|
-
if (index >= 0) {
|
|
44
|
-
this.queue.splice(index, 1);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
_canStart(now) {
|
|
48
|
-
if (this.active >= this.concurrency) {
|
|
49
|
-
return false;
|
|
50
|
-
}
|
|
51
|
-
if (this.requestsPerInterval == null || this.interval == null) {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
if (now - this.windowStart >= this.interval) {
|
|
55
|
-
this.windowStart = now;
|
|
56
|
-
this.startedInWindow = 0;
|
|
57
|
-
}
|
|
58
|
-
if (this.startedInWindow < this.requestsPerInterval) {
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
_schedule() {
|
|
64
|
-
if (this.waitingTimer) {
|
|
65
|
-
// There's already a timer waiting for the next window; let it fire.
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const now = Date.now();
|
|
69
|
-
while (this.queue.length > 0 && this._canStart(Date.now())) {
|
|
70
|
-
const task = this.queue.shift();
|
|
71
|
-
if (task.signal?.aborted) {
|
|
72
|
-
task.abortCleanup?.();
|
|
73
|
-
task.reject(task.signal.reason ?? new Error('Task aborted while queued'));
|
|
74
|
-
continue;
|
|
75
|
-
}
|
|
76
|
-
this.active++;
|
|
77
|
-
this.startedInWindow++;
|
|
78
|
-
const clearAbort = task.abortCleanup;
|
|
79
|
-
if (clearAbort) {
|
|
80
|
-
clearAbort();
|
|
81
|
-
task.abortCleanup = undefined;
|
|
82
|
-
}
|
|
83
|
-
Promise.resolve()
|
|
84
|
-
.then(() => task.fn())
|
|
85
|
-
.then((result) => task.resolve(result))
|
|
86
|
-
.catch((error) => task.reject(error))
|
|
87
|
-
.finally(() => {
|
|
88
|
-
this.active--;
|
|
89
|
-
this._schedule();
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
// If rate limit prevents starting now, schedule when the window resets
|
|
93
|
-
if (this.queue.length > 0 &&
|
|
94
|
-
this.requestsPerInterval != null &&
|
|
95
|
-
this.interval != null &&
|
|
96
|
-
!this._canStart(Date.now())) {
|
|
97
|
-
const wait = Math.max(0, this.windowStart + this.interval - Date.now());
|
|
98
|
-
this.waitingTimer = setTimeout(() => {
|
|
99
|
-
this.waitingTimer = undefined;
|
|
100
|
-
this._schedule();
|
|
101
|
-
}, wait);
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|