@cyanheads/git-mcp-server 1.2.4 → 2.0.2

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.
Files changed (105) hide show
  1. package/README.md +172 -285
  2. package/dist/config/index.js +69 -0
  3. package/dist/index.js +135 -0
  4. package/dist/mcp-server/server.js +572 -0
  5. package/dist/mcp-server/tools/gitAdd/index.js +7 -0
  6. package/dist/mcp-server/tools/gitAdd/logic.js +118 -0
  7. package/dist/mcp-server/tools/gitAdd/registration.js +73 -0
  8. package/dist/mcp-server/tools/gitBranch/index.js +7 -0
  9. package/dist/mcp-server/tools/gitBranch/logic.js +180 -0
  10. package/dist/mcp-server/tools/gitBranch/registration.js +72 -0
  11. package/dist/mcp-server/tools/gitCheckout/index.js +6 -0
  12. package/dist/mcp-server/tools/gitCheckout/logic.js +165 -0
  13. package/dist/mcp-server/tools/gitCheckout/registration.js +78 -0
  14. package/dist/mcp-server/tools/gitCherryPick/index.js +7 -0
  15. package/dist/mcp-server/tools/gitCherryPick/logic.js +115 -0
  16. package/dist/mcp-server/tools/gitCherryPick/registration.js +69 -0
  17. package/dist/mcp-server/tools/gitClean/index.js +7 -0
  18. package/dist/mcp-server/tools/gitClean/logic.js +110 -0
  19. package/dist/mcp-server/tools/gitClean/registration.js +98 -0
  20. package/dist/mcp-server/tools/gitClearWorkingDir/index.js +7 -0
  21. package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +35 -0
  22. package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +73 -0
  23. package/dist/mcp-server/tools/gitClone/index.js +7 -0
  24. package/dist/mcp-server/tools/gitClone/logic.js +136 -0
  25. package/dist/mcp-server/tools/gitClone/registration.js +44 -0
  26. package/dist/mcp-server/tools/gitCommit/index.js +7 -0
  27. package/dist/mcp-server/tools/gitCommit/logic.js +129 -0
  28. package/dist/mcp-server/tools/gitCommit/registration.js +100 -0
  29. package/dist/mcp-server/tools/gitDiff/index.js +6 -0
  30. package/dist/mcp-server/tools/gitDiff/logic.js +114 -0
  31. package/dist/mcp-server/tools/gitDiff/registration.js +74 -0
  32. package/dist/mcp-server/tools/gitFetch/index.js +6 -0
  33. package/dist/mcp-server/tools/gitFetch/logic.js +116 -0
  34. package/dist/mcp-server/tools/gitFetch/registration.js +71 -0
  35. package/dist/mcp-server/tools/gitInit/index.js +7 -0
  36. package/dist/mcp-server/tools/gitInit/logic.js +117 -0
  37. package/dist/mcp-server/tools/gitInit/registration.js +44 -0
  38. package/dist/mcp-server/tools/gitLog/index.js +6 -0
  39. package/dist/mcp-server/tools/gitLog/logic.js +148 -0
  40. package/dist/mcp-server/tools/gitLog/registration.js +71 -0
  41. package/dist/mcp-server/tools/gitMerge/index.js +7 -0
  42. package/dist/mcp-server/tools/gitMerge/logic.js +160 -0
  43. package/dist/mcp-server/tools/gitMerge/registration.js +77 -0
  44. package/dist/mcp-server/tools/gitPull/index.js +6 -0
  45. package/dist/mcp-server/tools/gitPull/logic.js +144 -0
  46. package/dist/mcp-server/tools/gitPull/registration.js +81 -0
  47. package/dist/mcp-server/tools/gitPush/index.js +6 -0
  48. package/dist/mcp-server/tools/gitPush/logic.js +188 -0
  49. package/dist/mcp-server/tools/gitPush/registration.js +81 -0
  50. package/dist/mcp-server/tools/gitRebase/index.js +7 -0
  51. package/dist/mcp-server/tools/gitRebase/logic.js +171 -0
  52. package/dist/mcp-server/tools/gitRebase/registration.js +72 -0
  53. package/dist/mcp-server/tools/gitRemote/index.js +7 -0
  54. package/dist/mcp-server/tools/gitRemote/logic.js +158 -0
  55. package/dist/mcp-server/tools/gitRemote/registration.js +76 -0
  56. package/dist/mcp-server/tools/gitReset/index.js +6 -0
  57. package/dist/mcp-server/tools/gitReset/logic.js +116 -0
  58. package/dist/mcp-server/tools/gitReset/registration.js +71 -0
  59. package/dist/mcp-server/tools/gitSetWorkingDir/index.js +7 -0
  60. package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +91 -0
  61. package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +78 -0
  62. package/dist/mcp-server/tools/gitShow/index.js +7 -0
  63. package/dist/mcp-server/tools/gitShow/logic.js +99 -0
  64. package/dist/mcp-server/tools/gitShow/registration.js +83 -0
  65. package/dist/mcp-server/tools/gitStash/index.js +7 -0
  66. package/dist/mcp-server/tools/gitStash/logic.js +161 -0
  67. package/dist/mcp-server/tools/gitStash/registration.js +84 -0
  68. package/dist/mcp-server/tools/gitStatus/index.js +7 -0
  69. package/dist/mcp-server/tools/gitStatus/logic.js +215 -0
  70. package/dist/mcp-server/tools/gitStatus/registration.js +77 -0
  71. package/dist/mcp-server/tools/gitTag/index.js +7 -0
  72. package/dist/mcp-server/tools/gitTag/logic.js +142 -0
  73. package/dist/mcp-server/tools/gitTag/registration.js +84 -0
  74. package/dist/types-global/errors.js +68 -0
  75. package/dist/types-global/mcp.js +59 -0
  76. package/dist/types-global/tool.js +1 -0
  77. package/dist/utils/errorHandler.js +237 -0
  78. package/dist/utils/idGenerator.js +148 -0
  79. package/dist/utils/index.js +11 -0
  80. package/dist/utils/jsonParser.js +78 -0
  81. package/dist/utils/logger.js +266 -0
  82. package/dist/utils/rateLimiter.js +177 -0
  83. package/dist/utils/requestContext.js +49 -0
  84. package/dist/utils/sanitization.js +371 -0
  85. package/dist/utils/tokenCounter.js +124 -0
  86. package/package.json +62 -17
  87. package/build/index.js +0 -54
  88. package/build/resources/descriptors.js +0 -77
  89. package/build/resources/diff.js +0 -241
  90. package/build/resources/file.js +0 -222
  91. package/build/resources/history.js +0 -242
  92. package/build/resources/index.js +0 -99
  93. package/build/resources/repository.js +0 -286
  94. package/build/server.js +0 -120
  95. package/build/services/error-service.js +0 -73
  96. package/build/services/git-service.js +0 -965
  97. package/build/tools/advanced.js +0 -526
  98. package/build/tools/branch.js +0 -296
  99. package/build/tools/index.js +0 -29
  100. package/build/tools/remote.js +0 -279
  101. package/build/tools/repository.js +0 -170
  102. package/build/tools/workdir.js +0 -445
  103. package/build/types/git.js +0 -7
  104. package/build/utils/global-settings.js +0 -64
  105. package/build/utils/validation.js +0 -108
@@ -0,0 +1,266 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import winston from 'winston';
5
+ import { config } from '../config/index.js'; // Import config for logger name
6
+ // Define the numeric severity for comparison (lower is more severe)
7
+ const mcpLevelSeverity = {
8
+ emerg: 0, alert: 1, crit: 2, error: 3, warning: 4, notice: 5, info: 6, debug: 7
9
+ };
10
+ // Map MCP levels to Winston's core levels for file logging
11
+ const mcpToWinstonLevel = {
12
+ debug: 'debug',
13
+ info: 'info',
14
+ notice: 'info', // Map notice to info for file logging
15
+ warning: 'warn',
16
+ error: 'error',
17
+ crit: 'error', // Map critical levels to error for file logging
18
+ alert: 'error',
19
+ emerg: 'error',
20
+ };
21
+ // Resolve __dirname for ESM
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = path.dirname(__filename);
24
+ // Project root assumed two levels above utils/
25
+ const projectRoot = path.resolve(__dirname, '..', '..');
26
+ const logsDir = path.join(projectRoot, 'logs');
27
+ // Security: ensure logsDir is within projectRoot
28
+ const resolvedLogsDir = path.resolve(logsDir);
29
+ const isLogsDirSafe = resolvedLogsDir === projectRoot || resolvedLogsDir.startsWith(projectRoot + path.sep);
30
+ if (!isLogsDirSafe) {
31
+ // Use console.error here as logger might not be initialized or safe
32
+ console.error(`FATAL: logs directory "${resolvedLogsDir}" is outside project root "${projectRoot}". File logging disabled.`);
33
+ }
34
+ /**
35
+ * Singleton Logger wrapping Winston, adapted for MCP.
36
+ * Logs to files and optionally sends MCP notifications/message.
37
+ */
38
+ class Logger {
39
+ static instance;
40
+ winstonLogger;
41
+ initialized = false;
42
+ mcpNotificationSender;
43
+ currentMcpLevel = 'info'; // Default MCP level
44
+ currentWinstonLevel = 'info'; // Default Winston level
45
+ constructor() { }
46
+ /**
47
+ * Initialize Winston logger for file transport. Must be called once at app start.
48
+ * Console transport is removed.
49
+ * @param level Initial minimum level to log ('info' default).
50
+ */
51
+ initialize(level = 'info') {
52
+ if (this.initialized) {
53
+ // Use console.warn as logger might be re-initializing
54
+ console.warn('Logger already initialized.');
55
+ return;
56
+ }
57
+ this.currentMcpLevel = level;
58
+ this.currentWinstonLevel = mcpToWinstonLevel[level];
59
+ // Ensure logs directory exists
60
+ if (isLogsDirSafe) {
61
+ try {
62
+ if (!fs.existsSync(resolvedLogsDir)) {
63
+ fs.mkdirSync(resolvedLogsDir, { recursive: true });
64
+ // Use console.log as logger isn't fully ready
65
+ console.log(`Created logs directory: ${resolvedLogsDir}`);
66
+ }
67
+ }
68
+ catch (err) {
69
+ // Use console.error as logger isn't fully ready
70
+ console.error(`Error creating logs directory at ${resolvedLogsDir}: ${err.message}. File logging disabled.`);
71
+ }
72
+ }
73
+ // Common format for files
74
+ const fileFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }),
75
+ // Use JSON format for file logs for easier parsing
76
+ winston.format.json());
77
+ const transports = [];
78
+ // Add file transports only if the directory is safe
79
+ if (isLogsDirSafe) {
80
+ transports.push(
81
+ // Log levels equal to or more severe than the specified level
82
+ new winston.transports.File({ filename: path.join(resolvedLogsDir, 'error.log'), level: 'error', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'warn.log'), level: 'warn', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'info.log'), level: 'info', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'debug.log'), level: 'debug', format: fileFormat }),
83
+ // Combined log captures everything based on the main logger level
84
+ new winston.transports.File({ filename: path.join(resolvedLogsDir, 'combined.log'), format: fileFormat }));
85
+ }
86
+ else {
87
+ // Use console.warn as logger isn't fully ready
88
+ console.warn("File logging disabled due to unsafe logs directory path.");
89
+ }
90
+ // Create logger with the initial Winston level and file transports
91
+ this.winstonLogger = winston.createLogger({
92
+ level: this.currentWinstonLevel, // Set Winston level for file logging
93
+ transports,
94
+ exitOnError: false
95
+ });
96
+ this.initialized = true;
97
+ // Log initialization message using the logger itself (will go to file)
98
+ this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}`);
99
+ }
100
+ /**
101
+ * Sets the function used to send MCP 'notifications/message'.
102
+ * This should be called by the server logic once an MCP connection
103
+ * supporting logging is established.
104
+ * @param sender The function to call for sending notifications.
105
+ */
106
+ setMcpNotificationSender(sender) {
107
+ this.mcpNotificationSender = sender;
108
+ const status = sender ? 'enabled' : 'disabled';
109
+ this.info(`MCP notification sending ${status}.`);
110
+ }
111
+ /**
112
+ * Dynamically sets the minimum logging level for both file logging and MCP notifications.
113
+ * @param newLevel The new minimum MCP log level.
114
+ */
115
+ setLevel(newLevel) {
116
+ if (!this.ensureInitialized()) {
117
+ // Use console.error as logger state is uncertain
118
+ console.error("Cannot set level: Logger not initialized.");
119
+ return;
120
+ }
121
+ // Validate the level
122
+ if (!(newLevel in mcpLevelSeverity)) {
123
+ this.warning(`Invalid MCP log level provided: ${newLevel}. Level not changed.`);
124
+ return;
125
+ }
126
+ this.currentMcpLevel = newLevel;
127
+ this.currentWinstonLevel = mcpToWinstonLevel[newLevel];
128
+ this.winstonLogger.level = this.currentWinstonLevel; // Update Winston level for files
129
+ this.info(`Log level set. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}`);
130
+ }
131
+ /** Get singleton instance. */
132
+ static getInstance() {
133
+ if (!Logger.instance) {
134
+ Logger.instance = new Logger();
135
+ }
136
+ return Logger.instance;
137
+ }
138
+ /** Ensures the logger has been initialized. */
139
+ ensureInitialized() {
140
+ if (!this.initialized || !this.winstonLogger) {
141
+ // Use console.warn as this indicates a programming error (calling log before init)
142
+ console.warn('Logger not initialized; message dropped.');
143
+ return false;
144
+ }
145
+ return true;
146
+ }
147
+ /** Centralized log processing */
148
+ log(level, msg, context, error) {
149
+ if (!this.ensureInitialized())
150
+ return;
151
+ // Check if message level is severe enough for current setting
152
+ if (mcpLevelSeverity[level] > mcpLevelSeverity[this.currentMcpLevel]) {
153
+ return; // Skip logging if level is less severe than current setting
154
+ }
155
+ const logData = { ...context }; // Copy context
156
+ const winstonLevel = mcpToWinstonLevel[level];
157
+ // Log to Winston (files)
158
+ if (error) {
159
+ // Include error details for Winston file log
160
+ logData.error = { message: error.message, stack: error.stack };
161
+ this.winstonLogger.log(winstonLevel, msg, logData);
162
+ }
163
+ else {
164
+ this.winstonLogger.log(winstonLevel, msg, logData);
165
+ }
166
+ // Send MCP notification if sender is configured
167
+ if (this.mcpNotificationSender) {
168
+ // Prepare data for MCP: combine message and context/error info
169
+ const mcpDataPayload = { message: msg };
170
+ if (context) {
171
+ mcpDataPayload.context = context;
172
+ }
173
+ if (error) {
174
+ // Include simplified error info for MCP notification
175
+ mcpDataPayload.error = { message: error.message };
176
+ // Optionally include stack in debug mode? Be cautious about size.
177
+ if (this.currentMcpLevel === 'debug' && error.stack) {
178
+ mcpDataPayload.error.stack = error.stack.substring(0, 500); // Limit stack trace size
179
+ }
180
+ }
181
+ try {
182
+ this.mcpNotificationSender(level, mcpDataPayload, config.mcpServerName);
183
+ }
184
+ catch (sendError) {
185
+ // Log failure to send MCP notification to file log
186
+ this.winstonLogger.error("Failed to send MCP log notification", {
187
+ originalLevel: level,
188
+ originalMessage: msg,
189
+ sendError: sendError instanceof Error ? sendError.message : String(sendError),
190
+ mcpPayload: mcpDataPayload // Log what we tried to send
191
+ });
192
+ }
193
+ }
194
+ }
195
+ // --- Public Logging Methods ---
196
+ /** Log debug message (level 7) */
197
+ debug(msg, context) {
198
+ this.log('debug', msg, context);
199
+ }
200
+ /** Log info message (level 6) */
201
+ info(msg, context) {
202
+ this.log('info', msg, context);
203
+ }
204
+ /** Log notice message (level 5) */
205
+ notice(msg, context) {
206
+ this.log('notice', msg, context);
207
+ }
208
+ /** Log warning message (level 4) */
209
+ warning(msg, context) {
210
+ this.log('warning', msg, context);
211
+ }
212
+ /** Log error message (level 3) */
213
+ error(msg, err, context) {
214
+ if (err instanceof Error) {
215
+ this.log('error', msg, context, err);
216
+ }
217
+ else {
218
+ // If err is not an Error object, treat it as additional context
219
+ const combinedContext = { ...(err || {}), ...(context || {}) };
220
+ this.log('error', msg, combinedContext);
221
+ }
222
+ }
223
+ /** Log critical message (level 2) */
224
+ crit(msg, err, context) {
225
+ if (err instanceof Error) {
226
+ this.log('crit', msg, context, err);
227
+ }
228
+ else {
229
+ const combinedContext = { ...(err || {}), ...(context || {}) };
230
+ this.log('crit', msg, combinedContext);
231
+ }
232
+ }
233
+ /** Log alert message (level 1) */
234
+ alert(msg, err, context) {
235
+ if (err instanceof Error) {
236
+ this.log('alert', msg, context, err);
237
+ }
238
+ else {
239
+ const combinedContext = { ...(err || {}), ...(context || {}) };
240
+ this.log('alert', msg, combinedContext);
241
+ }
242
+ }
243
+ /** Log emergency message (level 0) */
244
+ emerg(msg, err, context) {
245
+ if (err instanceof Error) {
246
+ this.log('emerg', msg, context, err);
247
+ }
248
+ else {
249
+ const combinedContext = { ...(err || {}), ...(context || {}) };
250
+ this.log('emerg', msg, combinedContext);
251
+ }
252
+ }
253
+ /** Log fatal message (alias for emergency, ensures process exit) */
254
+ fatal(msg, context, error) {
255
+ this.log('emerg', msg, context, error);
256
+ // Optionally add logic here to ensure process termination after logging fatal error
257
+ // Be careful with async operations here if you intend immediate exit.
258
+ // process.exit(1); // Consider if this is appropriate for your application's shutdown logic
259
+ }
260
+ }
261
+ // Export singleton instance
262
+ export const logger = Logger.getInstance();
263
+ // Initialize logger on import (can be configured later via setLevel/setMcpNotificationSender)
264
+ // Read initial level from env var or default to 'info'
265
+ const initialLogLevel = process.env.MCP_LOG_LEVEL || 'info';
266
+ logger.initialize(initialLogLevel);
@@ -0,0 +1,177 @@
1
+ import { BaseErrorCode, McpError } from '../types-global/errors.js';
2
+ import { logger } from './logger.js';
3
+ /**
4
+ * Generic rate limiter that can be used across the application
5
+ */
6
+ export class RateLimiter {
7
+ config;
8
+ /** Map storing rate limit data */
9
+ limits;
10
+ /** Cleanup interval timer */
11
+ cleanupTimer = null;
12
+ /** Default configuration */
13
+ static DEFAULT_CONFIG = {
14
+ windowMs: 15 * 60 * 1000, // 15 minutes
15
+ maxRequests: 100, // 100 requests per window
16
+ errorMessage: 'Rate limit exceeded. Please try again in {waitTime} seconds.',
17
+ skipInDevelopment: false,
18
+ cleanupInterval: 5 * 60 * 1000 // 5 minutes
19
+ };
20
+ /**
21
+ * Create a new rate limiter
22
+ * @param config Rate limiting configuration
23
+ */
24
+ constructor(config) {
25
+ this.config = config;
26
+ this.config = { ...RateLimiter.DEFAULT_CONFIG, ...config };
27
+ this.limits = new Map();
28
+ this.startCleanupTimer();
29
+ // Log initialization
30
+ logger.debug('RateLimiter initialized', {
31
+ windowMs: this.config.windowMs,
32
+ maxRequests: this.config.maxRequests,
33
+ cleanupInterval: this.config.cleanupInterval
34
+ });
35
+ }
36
+ /**
37
+ * Start the cleanup timer to periodically remove expired entries
38
+ */
39
+ startCleanupTimer() {
40
+ if (this.cleanupTimer) {
41
+ clearInterval(this.cleanupTimer);
42
+ }
43
+ const interval = this.config.cleanupInterval ?? RateLimiter.DEFAULT_CONFIG.cleanupInterval;
44
+ if (interval) {
45
+ this.cleanupTimer = setInterval(() => {
46
+ this.cleanupExpiredEntries();
47
+ }, interval);
48
+ // Ensure the timer doesn't prevent the process from exiting
49
+ if (this.cleanupTimer.unref) {
50
+ this.cleanupTimer.unref();
51
+ }
52
+ }
53
+ }
54
+ /**
55
+ * Clean up expired rate limit entries to prevent memory leaks
56
+ */
57
+ cleanupExpiredEntries() {
58
+ const now = Date.now();
59
+ let expiredCount = 0;
60
+ // Use a synchronized approach to avoid race conditions during cleanup
61
+ for (const [key, entry] of this.limits.entries()) {
62
+ if (now >= entry.resetTime) {
63
+ this.limits.delete(key);
64
+ expiredCount++;
65
+ }
66
+ }
67
+ if (expiredCount > 0) {
68
+ logger.debug(`Cleaned up ${expiredCount} expired rate limit entries`, {
69
+ totalRemaining: this.limits.size
70
+ });
71
+ }
72
+ }
73
+ /**
74
+ * Update rate limiter configuration
75
+ * @param config New configuration options
76
+ */
77
+ configure(config) {
78
+ this.config = { ...this.config, ...config };
79
+ // Restart cleanup timer if interval changed
80
+ if (config.cleanupInterval !== undefined) {
81
+ this.startCleanupTimer();
82
+ }
83
+ }
84
+ /**
85
+ * Get current configuration
86
+ * @returns Current rate limit configuration
87
+ */
88
+ getConfig() {
89
+ return { ...this.config };
90
+ }
91
+ /**
92
+ * Reset all rate limits
93
+ */
94
+ reset() {
95
+ this.limits.clear();
96
+ logger.debug('Rate limiter reset, all limits cleared');
97
+ }
98
+ /**
99
+ * Check if a request exceeds the rate limit
100
+ * @param key Unique identifier for the request source
101
+ * @param context Optional request context
102
+ * @throws {McpError} If rate limit is exceeded
103
+ */
104
+ check(key, context) {
105
+ // Skip in development if configured
106
+ if (this.config.skipInDevelopment && process.env.NODE_ENV === 'development') {
107
+ return;
108
+ }
109
+ // Generate key using custom generator if provided
110
+ const limitKey = this.config.keyGenerator
111
+ ? this.config.keyGenerator(key, context)
112
+ : key;
113
+ const now = Date.now();
114
+ // Accessing and updating the limit entry within a single function scope
115
+ // ensures atomicity in Node.js's single-threaded event loop for Map operations.
116
+ const limit = () => {
117
+ // Get current entry or create a new one if it doesn't exist or is expired
118
+ const entry = this.limits.get(limitKey);
119
+ // Create new entry or reset if expired
120
+ if (!entry || now >= entry.resetTime) {
121
+ const newEntry = {
122
+ count: 1,
123
+ resetTime: now + this.config.windowMs
124
+ };
125
+ this.limits.set(limitKey, newEntry);
126
+ return newEntry;
127
+ }
128
+ // Check if limit exceeded
129
+ if (entry.count >= this.config.maxRequests) {
130
+ const waitTime = Math.ceil((entry.resetTime - now) / 1000);
131
+ const errorMessage = this.config.errorMessage?.replace('{waitTime}', waitTime.toString()) ||
132
+ `Rate limit exceeded. Please try again in ${waitTime} seconds.`;
133
+ throw new McpError(BaseErrorCode.RATE_LIMITED, errorMessage, { waitTime, key: limitKey });
134
+ }
135
+ // Increment counter and return updated entry
136
+ entry.count++;
137
+ return entry;
138
+ };
139
+ // Execute the rate limiting logic
140
+ limit();
141
+ }
142
+ /**
143
+ * Get rate limit information for a key
144
+ * @param key The rate limit key
145
+ * @returns Current rate limit status or null if no record exists
146
+ */
147
+ getStatus(key) {
148
+ const entry = this.limits.get(key);
149
+ if (!entry) {
150
+ return null;
151
+ }
152
+ return {
153
+ current: entry.count,
154
+ limit: this.config.maxRequests,
155
+ remaining: Math.max(0, this.config.maxRequests - entry.count),
156
+ resetTime: entry.resetTime
157
+ };
158
+ }
159
+ /**
160
+ * Stop the cleanup timer when the limiter is no longer needed
161
+ */
162
+ dispose() {
163
+ if (this.cleanupTimer) {
164
+ clearInterval(this.cleanupTimer);
165
+ this.cleanupTimer = null;
166
+ }
167
+ // Clear all entries
168
+ this.limits.clear();
169
+ }
170
+ }
171
+ /**
172
+ * Create and export a default rate limiter instance
173
+ */
174
+ export const rateLimiter = new RateLimiter({
175
+ windowMs: 15 * 60 * 1000, // 15 minutes
176
+ maxRequests: 100 // 100 requests per window
177
+ });
@@ -0,0 +1,49 @@
1
+ import { logger } from './logger.js';
2
+ import { generateUUID } from './idGenerator.js'; // Import generateUUID
3
+ // Direct instance for request context utilities
4
+ const requestContextServiceInstance = {
5
+ config: {},
6
+ /**
7
+ * Configure service settings
8
+ * @param config New configuration
9
+ * @returns Updated configuration
10
+ */
11
+ configure(config) {
12
+ this.config = {
13
+ ...this.config,
14
+ ...config
15
+ };
16
+ logger.debug('RequestContext configuration updated', { config: this.config });
17
+ return { ...this.config };
18
+ },
19
+ /**
20
+ * Get current configuration
21
+ * @returns Current configuration
22
+ */
23
+ getConfig() {
24
+ return { ...this.config };
25
+ },
26
+ /**
27
+ * Create a request context with unique ID and timestamp
28
+ * @param additionalContext Additional context properties
29
+ * @returns Request context object
30
+ */
31
+ createRequestContext(additionalContext = {}) {
32
+ const requestId = generateUUID(); // Use imported generateUUID
33
+ const timestamp = new Date().toISOString();
34
+ return {
35
+ requestId,
36
+ timestamp,
37
+ ...additionalContext
38
+ };
39
+ },
40
+ // generateSecureRandomString function removed as it was unused and redundant
41
+ };
42
+ // Initialize logger message
43
+ logger.debug('RequestContext service initialized');
44
+ // Export the instance directly
45
+ export const requestContextService = requestContextServiceInstance;
46
+ // Removed delegate functions and default export for simplicity.
47
+ // Users should import and use `requestContextService` directly.
48
+ // e.g., import { requestContextService } from './requestContext.js';
49
+ // requestContextService.createRequestContext();