Spaces:
Sleeping
Sleeping
| import { sample, random, shuffle } from 'lodash-es'; | |
| import logger from '../utils/logger.js'; | |
| class CodeGenerator { | |
| constructor(config) { | |
| this.config = config; | |
| this.bugProbability = config.activity.bugProbability; | |
| this.typoProbability = config.activity.typoProbability; | |
| } | |
| generateProjectStructure() { | |
| const structures = [ | |
| { | |
| name: 'utils', | |
| files: [ | |
| { path: 'src/utils/helpers.js', type: 'utility' }, | |
| { path: 'src/utils/validators.js', type: 'validator' }, | |
| { path: 'src/utils/formatters.js', type: 'formatter' }, | |
| ], | |
| }, | |
| { | |
| name: 'services', | |
| files: [ | |
| { path: 'src/services/api.js', type: 'api' }, | |
| { path: 'src/services/cache.js', type: 'cache' }, | |
| { path: 'src/services/logger.js', type: 'logger' }, | |
| ], | |
| }, | |
| { | |
| name: 'models', | |
| files: [ | |
| { path: 'src/models/user.js', type: 'model' }, | |
| { path: 'src/models/product.js', type: 'model' }, | |
| { path: 'src/models/order.js', type: 'model' }, | |
| ], | |
| }, | |
| { | |
| name: 'middleware', | |
| files: [ | |
| { path: 'src/middleware/auth.js', type: 'auth' }, | |
| { path: 'src/middleware/errorHandler.js', type: 'error' }, | |
| { path: 'src/middleware/rateLimiter.js', type: 'rateLimiter' }, | |
| ], | |
| }, | |
| ]; | |
| return sample(structures); | |
| } | |
| generateFileContent(fileType, context = {}) { | |
| const generators = { | |
| utility: () => this._generateUtility(context), | |
| validator: () => this._generateValidator(context), | |
| formatter: () => this._generateFormatter(context), | |
| api: () => this._generateApiService(context), | |
| cache: () => this._generateCache(context), | |
| logger: () => this._generateLogger(context), | |
| model: () => this._generateModel(context), | |
| auth: () => this._generateAuthMiddleware(context), | |
| error: () => this._generateErrorHandler(context), | |
| rateLimiter: () => this._generateRateLimiter(context), | |
| }; | |
| const generator = generators[fileType] || generators.utility; | |
| let content = generator(); | |
| if (Math.random() < this.bugProbability) { | |
| content = this._injectBug(content); | |
| } | |
| if (Math.random() < this.typoProbability) { | |
| content = this._injectTypo(content); | |
| } | |
| return content; | |
| } | |
| _generateUtility(context) { | |
| const utilities = [ | |
| this._debounceFunction(), | |
| this._throttleFunction(), | |
| this._deepCloneFunction(), | |
| this._flattenObject(), | |
| this._retryFunction(), | |
| this._eventEmitter(), | |
| this._memoizeFunction(), | |
| this._pipeFunction(), | |
| ]; | |
| return sample(utilities); | |
| } | |
| _debounceFunction() { | |
| return `/** | |
| * Creates a debounced function that delays invoking func until | |
| * after wait milliseconds have elapsed since the last invocation. | |
| * @param {Function} func - The function to debounce | |
| * @param {number} wait - Milliseconds to wait | |
| * @returns {Function} Debounced function | |
| */ | |
| export function debounce(func, wait = 300) { | |
| let timeoutId = null; | |
| return function (...args) { | |
| const later = () => { | |
| timeoutId = null; | |
| func.apply(this, args); | |
| }; | |
| clearTimeout(timeoutId); | |
| timeoutId = setTimeout(later, wait); | |
| }; | |
| } | |
| /** | |
| * Creates a throttled function that only invokes func at most | |
| * once per every wait milliseconds. | |
| * @param {Function} func - The function to throttle | |
| * @param {number} wait - Milliseconds to wait | |
| * @returns {Function} Throttled function | |
| */ | |
| export function throttle(func, wait = 300) { | |
| let lastCall = 0; | |
| let timeoutId = null; | |
| return function (...args) { | |
| const now = Date.now(); | |
| const remaining = wait - (now - lastCall); | |
| if (remaining <= 0) { | |
| if (timeoutId) { | |
| clearTimeout(timeoutId); | |
| timeoutId = null; | |
| } | |
| lastCall = now; | |
| func.apply(this, args); | |
| } else if (!timeoutId) { | |
| timeoutId = setTimeout(() => { | |
| lastCall = Date.now(); | |
| timeoutId = null; | |
| func.apply(this, args); | |
| }, remaining); | |
| } | |
| }; | |
| } | |
| `; | |
| } | |
| _throttleFunction() { | |
| return `/** | |
| * Deep clones an object, handling circular references. | |
| * @param {*} obj - The object to clone | |
| * @param {WeakMap} seen - Map to track circular references | |
| * @returns {*} Deep cloned object | |
| */ | |
| export function deepClone(obj, seen = new WeakMap()) { | |
| if (obj === null || typeof obj !== 'object') { | |
| return obj; | |
| } | |
| if (seen.has(obj)) { | |
| return seen.get(obj); | |
| } | |
| if (obj instanceof Date) { | |
| return new Date(obj.getTime()); | |
| } | |
| if (obj instanceof RegExp) { | |
| return new RegExp(obj.source, obj.flags); | |
| } | |
| if (Array.isArray(obj)) { | |
| const cloned = obj.map(item => deepClone(item, seen)); | |
| seen.set(obj, cloned); | |
| return cloned; | |
| } | |
| const cloned = Object.create(Object.getPrototypeOf(obj)); | |
| seen.set(obj, cloned); | |
| for (const [key, value] of Object.entries(obj)) { | |
| cloned[key] = deepClone(value, seen); | |
| } | |
| return cloned; | |
| } | |
| /** | |
| * Checks if two values are deeply equal. | |
| * @param {*} a - First value | |
| * @param {*} b - Second value | |
| * @returns {boolean} True if deeply equal | |
| */ | |
| export function deepEqual(a, b) { | |
| if (a === b) return true; | |
| if (a == null || b == null) return false; | |
| if (typeof a !== typeof b) return false; | |
| if (Array.isArray(a)) { | |
| if (!Array.isArray(b) || a.length !== b.length) return false; | |
| return a.every((item, i) => deepEqual(item, b[i])); | |
| } | |
| if (typeof a === 'object') { | |
| const keysA = Object.keys(a); | |
| const keysB = Object.keys(b); | |
| if (keysA.length !== keysB.length) return false; | |
| return keysA.every(key => deepEqual(a[key], b[key])); | |
| } | |
| return false; | |
| } | |
| `; | |
| } | |
| _deepCloneFunction() { | |
| return `/** | |
| * Flattens a nested object into a single level with dot notation keys. | |
| * @param {Object} obj - The object to flatten | |
| * @param {string} prefix - Key prefix for recursion | |
| * @returns {Object} Flattened object | |
| */ | |
| export function flattenObject(obj, prefix = '') { | |
| return Object.keys(obj).reduce((acc, key) => { | |
| const fullKey = prefix ? \`\${prefix}.\${key}\` : key; | |
| const value = obj[key]; | |
| if (typeof value === 'object' && value !== null && !Array.isArray(value)) { | |
| Object.assign(acc, flattenObject(value, fullKey)); | |
| } else { | |
| acc[fullKey] = value; | |
| } | |
| return acc; | |
| }, {}); | |
| } | |
| /** | |
| * Unflattens a dot-notation object back to nested structure. | |
| * @param {Object} obj - The flattened object | |
| * @returns {Object} Nested object | |
| */ | |
| export function unflattenObject(obj) { | |
| const result = {}; | |
| for (const [key, value] of Object.entries(obj)) { | |
| const parts = key.split('.'); | |
| let current = result; | |
| for (let i = 0; i < parts.length - 1; i++) { | |
| const part = parts[i]; | |
| current[part] = current[part] || {}; | |
| current = current[part]; | |
| } | |
| current[parts[parts.length - 1]] = value; | |
| } | |
| return result; | |
| } | |
| `; | |
| } | |
| _flattenObject() { | |
| return `/** | |
| * Retries an async function with exponential backoff. | |
| * @param {Function} fn - Async function to retry | |
| * @param {Object} options - Retry options | |
| * @param {number} options.maxRetries - Maximum retry attempts | |
| * @param {number} options.baseDelay - Base delay in ms | |
| * @param {number} options.maxDelay - Maximum delay in ms | |
| * @returns {Promise<*>} Result of the function | |
| */ | |
| export async function retry(fn, { maxRetries = 3, baseDelay = 1000, maxDelay = 10000 } = {}) { | |
| let lastError; | |
| for (let attempt = 0; attempt <= maxRetries; attempt++) { | |
| try { | |
| return await fn(); | |
| } catch (error) { | |
| lastError = error; | |
| if (attempt === maxRetries) { | |
| break; | |
| } | |
| const delay = Math.min(baseDelay * Math.pow(2, attempt), maxDelay); | |
| const jitter = delay * 0.1 * Math.random(); | |
| await new Promise(resolve => setTimeout(resolve, delay + jitter)); | |
| } | |
| } | |
| throw lastError; | |
| } | |
| /** | |
| * Creates a timeout wrapper for a promise. | |
| * @param {Promise} promise - The promise to wrap | |
| * @param {number} ms - Timeout in milliseconds | |
| * @param {Error} error - Error to throw on timeout | |
| * @returns {Promise} Wrapped promise | |
| */ | |
| export function withTimeout(promise, ms, error = new Error('Operation timed out')) { | |
| let timeoutId; | |
| const timeout = new Promise((_, reject) => { | |
| timeoutId = setTimeout(() => reject(error), ms); | |
| }); | |
| return Promise.race([promise, timeout]).finally(() => { | |
| clearTimeout(timeoutId); | |
| }); | |
| } | |
| `; | |
| } | |
| _retryFunction() { | |
| return `/** | |
| * Simple event emitter implementation. | |
| */ | |
| export class EventEmitter { | |
| constructor() { | |
| this._listeners = new Map(); | |
| } | |
| /** | |
| * Register a listener for an event. | |
| * @param {string} event - Event name | |
| * @param {Function} listener - Callback function | |
| * @returns {Function} Unsubscribe function | |
| */ | |
| on(event, listener) { | |
| if (!this._listeners.has(event)) { | |
| this._listeners.set(event, []); | |
| } | |
| this._listeners.get(event).push(listener); | |
| return () => this.off(event, listener); | |
| } | |
| /** | |
| * Register a one-time listener. | |
| * @param {string} event - Event name | |
| * @param {Function} listener - Callback function | |
| */ | |
| once(event, listener) { | |
| const wrapper = (...args) => { | |
| listener.apply(this, args); | |
| this.off(event, wrapper); | |
| }; | |
| this.on(event, wrapper); | |
| } | |
| /** | |
| * Remove a listener. | |
| * @param {string} event - Event name | |
| * @param {Function} listener - Callback function | |
| */ | |
| off(event, listener) { | |
| if (!this._listeners.has(event)) return; | |
| const listeners = this._listeners.get(event); | |
| const index = listeners.indexOf(listener); | |
| if (index !== -1) { | |
| listeners.splice(index, 1); | |
| } | |
| } | |
| /** | |
| * Emit an event. | |
| * @param {string} event - Event name | |
| * @param {...*} args - Arguments to pass to listeners | |
| */ | |
| emit(event, ...args) { | |
| if (!this._listeners.has(event)) return; | |
| const listeners = [...this._listeners.get(event)]; | |
| for (const listener of listeners) { | |
| try { | |
| listener.apply(this, args); | |
| } catch (error) { | |
| console.error(\`Error in event listener for "\${event}":\`, error); | |
| } | |
| } | |
| } | |
| } | |
| `; | |
| } | |
| _eventEmitter() { | |
| return `/** | |
| * Creates a memoized version of a function that caches results. | |
| * @param {Function} fn - Function to memoize | |
| * @param {Function} [resolver] - Custom cache key resolver | |
| * @returns {Function} Memoized function | |
| */ | |
| export function memoize(fn, resolver = null) { | |
| const cache = new Map(); | |
| const memoized = function (...args) { | |
| const key = resolver ? resolver.apply(this, args) : args[0]; | |
| if (cache.has(key)) { | |
| return cache.get(key); | |
| } | |
| const result = fn.apply(this, args); | |
| cache.set(key, result); | |
| return result; | |
| }; | |
| memoized.cache = cache; | |
| memoized.clear = () => { | |
| cache.clear(); | |
| }; | |
| return memoized; | |
| } | |
| /** | |
| * Creates a function that returns the result of the first | |
| * predicate that returns a truthy value. | |
| * @param {...Function} predicates - Functions to try | |
| * @returns {Function} Combined function | |
| */ | |
| export function firstOf(...predicates) { | |
| return function (...args) { | |
| for (const predicate of predicates) { | |
| const result = predicate.apply(this, args); | |
| if (result != null) { | |
| return result; | |
| } | |
| } | |
| return undefined; | |
| }; | |
| } | |
| `; | |
| } | |
| _memoizeFunction() { | |
| return `/** | |
| * Creates a function pipeline where the output of each function | |
| * is passed as input to the next. | |
| * @param {...Function} functions - Functions to compose | |
| * @returns {Function} Pipeline function | |
| */ | |
| export function pipe(...functions) { | |
| return function (initialValue) { | |
| return functions.reduce((value, fn) => fn(value), initialValue); | |
| }; | |
| } | |
| /** | |
| * Creates a function that composes functions right-to-left. | |
| * @param {...Function} functions - Functions to compose | |
| * @returns {Function} Composed function | |
| */ | |
| export function compose(...functions) { | |
| return function (initialValue) { | |
| return functions.reduceRight((value, fn) => fn(value), initialValue); | |
| }; | |
| } | |
| /** | |
| * Creates a curried version of a function. | |
| * @param {Function} fn - Function to curry | |
| * @param {number} [arity] - Number of arguments expected | |
| * @returns {Function} Curried function | |
| */ | |
| export function curry(fn, arity = fn.length) { | |
| return function curried(...args) { | |
| if (args.length >= arity) { | |
| return fn.apply(this, args); | |
| } | |
| return (...moreArgs) => curried.apply(this, [...args, ...moreArgs]); | |
| }; | |
| } | |
| `; | |
| } | |
| _pipeFunction() { | |
| return `/** | |
| * Validation utilities for common data types. | |
| */ | |
| const EMAIL_REGEX = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/; | |
| const PHONE_REGEX = /^\\+?[1-9]\\d{1,14}$/; | |
| const URL_REGEX = /^https?:\\/\\/[^\\s$.?#].[^\\s]*$/; | |
| /** | |
| * Validates an email address. | |
| * @param {string} email - Email to validate | |
| * @returns {boolean} True if valid | |
| */ | |
| export function isValidEmail(email) { | |
| if (typeof email !== 'string') return false; | |
| return EMAIL_REGEX.test(email.trim()); | |
| } | |
| /** | |
| * Validates a phone number (E.164 format). | |
| * @param {string} phone - Phone number to validate | |
| * @returns {boolean} True if valid | |
| */ | |
| export function isValidPhone(phone) { | |
| if (typeof phone !== 'string') return false; | |
| return PHONE_REGEX.test(phone.replace(/[\\s-()]/g, '')); | |
| } | |
| /** | |
| * Validates a URL. | |
| * @param {string} url - URL to validate | |
| * @returns {boolean} True if valid | |
| */ | |
| export function isValidUrl(url) { | |
| if (typeof url !== 'string') return false; | |
| try { | |
| new URL(url); | |
| return true; | |
| } catch { | |
| return false; | |
| } | |
| } | |
| /** | |
| * Validates that a string is not empty or whitespace. | |
| * @param {string} str - String to validate | |
| * @returns {boolean} True if not empty | |
| */ | |
| export function isNonEmptyString(str) { | |
| return typeof str === 'string' && str.trim().length > 0; | |
| } | |
| /** | |
| * Validates that a value is a positive integer. | |
| * @param {*} value - Value to validate | |
| * @returns {boolean} True if positive integer | |
| */ | |
| export function isPositiveInt(value) { | |
| return Number.isInteger(value) && value > 0; | |
| } | |
| /** | |
| * Creates a validator chain for an object. | |
| * @param {Object} schema - Validation schema | |
| * @returns {Object} Validation result | |
| */ | |
| export function validateObject(schema, data) { | |
| const errors = []; | |
| for (const [field, rules] of Object.entries(schema)) { | |
| const value = data[field]; | |
| if (rules.required && (value === undefined || value === null)) { | |
| errors.push(\`\${field} is required\`); | |
| continue; | |
| } | |
| if (value !== undefined && rules.type && typeof value !== rules.type) { | |
| errors.push(\`\${field} must be of type \${rules.type}\`); | |
| } | |
| if (value !== undefined && rules.validate && !rules.validate(value)) { | |
| errors.push(\`\${field} failed custom validation\`); | |
| } | |
| } | |
| return { | |
| isValid: errors.length === 0, | |
| errors, | |
| }; | |
| } | |
| `; | |
| } | |
| _generateValidator() { | |
| return `/** | |
| * Formatting utilities for common data transformations. | |
| */ | |
| /** | |
| * Formats a number as currency. | |
| * @param {number} amount - The amount to format | |
| * @param {string} currency - Currency code (default: USD) | |
| * @param {string} locale - Locale string (default: en-US) | |
| * @returns {string} Formatted currency string | |
| */ | |
| export function formatCurrency(amount, currency = 'USD', locale = 'en-US') { | |
| if (typeof amount !== 'number' || isNaN(amount)) { | |
| throw new TypeError('Amount must be a valid number'); | |
| } | |
| return new Intl.NumberFormat(locale, { | |
| style: 'currency', | |
| currency, | |
| minimumFractionDigits: 2, | |
| maximumFractionDigits: 2, | |
| }).format(amount); | |
| } | |
| /** | |
| * Formats a date to a readable string. | |
| * @param {Date|string|number} date - Date to format | |
| * @param {Object} options - Formatting options | |
| * @returns {string} Formatted date string | |
| */ | |
| export function formatDate(date, options = {}) { | |
| const dateObj = date instanceof Date ? date : new Date(date); | |
| if (isNaN(dateObj.getTime())) { | |
| throw new TypeError('Invalid date'); | |
| } | |
| const defaultOptions = { | |
| year: 'numeric', | |
| month: 'short', | |
| day: 'numeric', | |
| ...options, | |
| }; | |
| return dateObj.toLocaleDateString('en-US', defaultOptions); | |
| } | |
| /** | |
| * Truncates a string to a maximum length. | |
| * @param {string} str - String to truncate | |
| * @param {number} maxLength - Maximum length | |
| * @param {string} suffix - Suffix to add (default: '...') | |
| * @returns {string} Truncated string | |
| */ | |
| export function truncate(str, maxLength, suffix = '...') { | |
| if (typeof str !== 'string') return ''; | |
| if (str.length <= maxLength) return str; | |
| return str.slice(0, maxLength - suffix.length) + suffix; | |
| } | |
| /** | |
| * Capitalizes the first letter of a string. | |
| * @param {string} str - String to capitalize | |
| * @returns {string} Capitalized string | |
| */ | |
| export function capitalize(str) { | |
| if (typeof str !== 'string' || str.length === 0) return ''; | |
| return str.charAt(0).toUpperCase() + str.slice(1); | |
| } | |
| /** | |
| * Converts a string to slug format. | |
| * @param {string} str - String to slugify | |
| * @returns {string} Slugified string | |
| */ | |
| export function slugify(str) { | |
| return str | |
| .toLowerCase() | |
| .trim() | |
| .replace(/[^\\w\\s-]/g, '') | |
| .replace(/[\\s_-]+/g, '-') | |
| .replace(/^-+|-+$/g, ''); | |
| } | |
| /** | |
| * Pads a number with leading zeros. | |
| * @param {number} num - Number to pad | |
| * @param {number} length - Target length | |
| * @returns {string} Padded number string | |
| */ | |
| export function padNumber(num, length = 2) { | |
| return String(num).padStart(length, '0'); | |
| } | |
| `; | |
| } | |
| _generateFormatter() { | |
| return `/** | |
| * API service with request queuing and response caching. | |
| */ | |
| class ApiService { | |
| constructor(baseURL, options = {}) { | |
| this.baseURL = baseURL.replace(/\\/$/, ''); | |
| this.timeout = options.timeout || 10000; | |
| this.retries = options.retries || 3; | |
| this._cache = new Map(); | |
| this._queue = []; | |
| this._processing = false; | |
| } | |
| /** | |
| * Makes a GET request with caching. | |
| * @param {string} path - Request path | |
| * @param {Object} params - Query parameters | |
| * @returns {Promise<Object>} Response data | |
| */ | |
| async get(path, params = {}) { | |
| const cacheKey = this._buildCacheKey(path, params); | |
| if (this._cache.has(cacheKey)) { | |
| const cached = this._cache.get(cacheKey); | |
| if (Date.now() - cached.timestamp < 60000) { | |
| return cached.data; | |
| } | |
| } | |
| const data = await this._request('GET', path, { params }); | |
| this._cache.set(cacheKey, { data, timestamp: Date.now() }); | |
| return data; | |
| } | |
| /** | |
| * Makes a POST request. | |
| * @param {string} path - Request path | |
| * @param {Object} body - Request body | |
| * @returns {Promise<Object>} Response data | |
| */ | |
| async post(path, body = {}) { | |
| this._cache.clear(); | |
| return this._request('POST', path, { body }); | |
| } | |
| /** | |
| * Makes a PUT request. | |
| * @param {string} path - Request path | |
| * @param {Object} body - Request body | |
| * @returns {Promise<Object>} Response data | |
| */ | |
| async put(path, body = {}) { | |
| this._cache.clear(); | |
| return this._request('PUT', path, { body }); | |
| } | |
| /** | |
| * Makes a DELETE request. | |
| * @param {string} path - Request path | |
| * @returns {Promise<Object>} Response data | |
| */ | |
| async delete(path) { | |
| this._cache.clear(); | |
| return this._request('DELETE', path); | |
| } | |
| /** | |
| * Queues a request for batch processing. | |
| * @param {string} method - HTTP method | |
| * @param {string} path - Request path | |
| * @param {Object} options - Request options | |
| */ | |
| queue(method, path, options = {}) { | |
| return new Promise((resolve, reject) => { | |
| this._queue.push({ method, path, options, resolve, reject }); | |
| if (!this._processing) { | |
| this._processQueue(); | |
| } | |
| }); | |
| } | |
| async _processQueue() { | |
| this._processing = true; | |
| while (this._queue.length > 0) { | |
| const batch = this._queue.splice(0, 5); | |
| await Promise.allSettled( | |
| batch.map(({ method, path, options, resolve, reject }) => | |
| this._request(method, path, options) | |
| .then(resolve) | |
| .catch(reject) | |
| ) | |
| ); | |
| } | |
| this._processing = false; | |
| } | |
| async _request(method, path, options = {}) { | |
| const url = new URL(path, this.baseURL); | |
| if (options.params) { | |
| Object.entries(options.params).forEach(([key, value]) => { | |
| if (value !== undefined) url.searchParams.append(key, value); | |
| }); | |
| } | |
| const controller = new AbortController(); | |
| const timeoutId = setTimeout(() => controller.abort(), this.timeout); | |
| try { | |
| const response = await fetch(url.toString(), { | |
| method, | |
| headers: { 'Content-Type': 'application/json' }, | |
| body: options.body ? JSON.stringify(options.body) : undefined, | |
| signal: controller.signal, | |
| }); | |
| if (!response.ok) { | |
| throw new Error(\`HTTP \${response.status}: \${response.statusText}\`); | |
| } | |
| return response.json(); | |
| } finally { | |
| clearTimeout(timeoutId); | |
| } | |
| } | |
| _buildCacheKey(path, params) { | |
| return \`\${path}?\${JSON.stringify(params)}\`; | |
| } | |
| clearCache() { | |
| this._cache.clear(); | |
| } | |
| } | |
| export default ApiService; | |
| `; | |
| } | |
| _generateApiService() { | |
| return `/** | |
| * In-memory cache with TTL and size limits. | |
| */ | |
| class Cache { | |
| constructor(options = {}) { | |
| this._store = new Map(); | |
| this._maxSize = options.maxSize || 1000; | |
| this._defaultTTL = options.defaultTTL || 300000; // 5 minutes | |
| } | |
| /** | |
| * Gets a value from cache. | |
| * @param {string} key - Cache key | |
| * @returns {*|null} Cached value or null | |
| */ | |
| get(key) { | |
| const entry = this._store.get(key); | |
| if (!entry) return null; | |
| if (entry.expiry < Date.now()) { | |
| this._store.delete(key); | |
| return null; | |
| } | |
| entry.lastAccessed = Date.now(); | |
| return entry.value; | |
| } | |
| /** | |
| * Sets a value in cache. | |
| * @param {string} key - Cache key | |
| * @param {*} value - Value to cache | |
| * @param {number} [ttl] - Time to live in ms | |
| */ | |
| set(key, value, ttl = this._defaultTTL) { | |
| if (this._store.size >= this._maxSize) { | |
| this._evictOldest(); | |
| } | |
| this._store.set(key, { | |
| value, | |
| expiry: Date.now() + ttl, | |
| createdAt: Date.now(), | |
| lastAccessed: Date.now(), | |
| }); | |
| } | |
| /** | |
| * Deletes a value from cache. | |
| * @param {string} key - Cache key | |
| * @returns {boolean} True if key existed | |
| */ | |
| delete(key) { | |
| return this._store.delete(key); | |
| } | |
| /** | |
| * Checks if a key exists and is not expired. | |
| * @param {string} key - Cache key | |
| * @returns {boolean} True if key exists | |
| */ | |
| has(key) { | |
| return this.get(key) !== null; | |
| } | |
| /** | |
| * Gets or computes a value. | |
| * @param {string} key - Cache key | |
| * @param {Function} computeFn - Function to compute value | |
| * @param {number} [ttl] - Time to live in ms | |
| * @returns {*} Cached or computed value | |
| */ | |
| async getOrCompute(key, computeFn, ttl = this._defaultTTL) { | |
| const cached = this.get(key); | |
| if (cached !== null) return cached; | |
| const value = await computeFn(); | |
| this.set(key, value, ttl); | |
| return value; | |
| } | |
| /** | |
| * Clears all expired entries. | |
| * @returns {number} Number of entries removed | |
| */ | |
| prune() { | |
| const now = Date.now(); | |
| let removed = 0; | |
| for (const [key, entry] of this._store.entries()) { | |
| if (entry.expiry < now) { | |
| this._store.delete(key); | |
| removed++; | |
| } | |
| } | |
| return removed; | |
| } | |
| /** | |
| * Clears the entire cache. | |
| */ | |
| clear() { | |
| this._store.clear(); | |
| } | |
| /** | |
| * Gets cache statistics. | |
| * @returns {Object} Cache stats | |
| */ | |
| getStats() { | |
| const now = Date.now(); | |
| let expired = 0; | |
| let active = 0; | |
| for (const entry of this._store.values()) { | |
| if (entry.expiry < now) { | |
| expired++; | |
| } else { | |
| active++; | |
| } | |
| } | |
| return { | |
| total: this._store.size, | |
| active, | |
| expired, | |
| maxSize: this._maxSize, | |
| }; | |
| } | |
| _evictOldest() { | |
| let oldestKey = null; | |
| let oldestTime = Infinity; | |
| for (const [key, entry] of this._store.entries()) { | |
| if (entry.lastAccessed < oldestTime) { | |
| oldestTime = entry.lastAccessed; | |
| oldestKey = key; | |
| } | |
| } | |
| if (oldestKey) { | |
| this._store.delete(oldestKey); | |
| } | |
| } | |
| } | |
| export default Cache; | |
| `; | |
| } | |
| _generateCache() { | |
| return `/** | |
| * Structured logger with levels and output formatting. | |
| */ | |
| const LEVELS = { | |
| debug: 0, | |
| info: 1, | |
| warn: 2, | |
| error: 3, | |
| }; | |
| class Logger { | |
| constructor(options = {}) { | |
| this.level = LEVELS[options.level] ?? LEVELS.info; | |
| this.prefix = options.prefix || ''; | |
| this.output = options.output || console; | |
| } | |
| /** | |
| * Creates a child logger with additional prefix. | |
| * @param {string} prefix - Child prefix | |
| * @returns {Logger} Child logger | |
| */ | |
| child(prefix) { | |
| return new Logger({ | |
| level: Object.keys(LEVELS).find(k => LEVELS[k] === this.level), | |
| prefix: this.prefix ? \`\${this.prefix}:\${prefix}\` : prefix, | |
| output: this.output, | |
| }); | |
| } | |
| debug(message, ...args) { | |
| this._log('debug', message, args); | |
| } | |
| info(message, ...args) { | |
| this._log('info', message, args); | |
| } | |
| warn(message, ...args) { | |
| this._log('warn', message, args); | |
| } | |
| error(message, ...args) { | |
| this._log('error', message, args); | |
| } | |
| _log(level, message, args) { | |
| if (LEVELS[level] < this.level) return; | |
| const timestamp = new Date().toISOString(); | |
| const prefix = this.prefix ? \`[\${this.prefix}]\` : ''; | |
| const formatted = \`\${timestamp} \${level.toUpperCase().padEnd(5)} \${prefix} \${message}\`; | |
| const method = level === 'error' ? 'error' : level === 'warn' ? 'warn' : 'log'; | |
| this.output[method](formatted, ...args); | |
| } | |
| } | |
| export default Logger; | |
| `; | |
| } | |
| _generateLogger() { | |
| return `/** | |
| * User model with validation and serialization. | |
| */ | |
| class User { | |
| constructor(data) { | |
| this.id = data.id || crypto.randomUUID(); | |
| this.email = data.email; | |
| this.name = data.name; | |
| this.role = data.role || 'user'; | |
| this.createdAt = data.createdAt || new Date(); | |
| this.updatedAt = new Date(); | |
| this.isActive = data.isActive ?? true; | |
| } | |
| /** | |
| * Validates user data. | |
| * @param {Object} data - User data | |
| * @returns {{valid: boolean, errors: string[]}} Validation result | |
| */ | |
| static validate(data) { | |
| const errors = []; | |
| if (!data.email || typeof data.email !== 'string') { | |
| errors.push('Email is required and must be a string'); | |
| } else if (!/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(data.email)) { | |
| errors.push('Invalid email format'); | |
| } | |
| if (!data.name || typeof data.name !== 'string' || data.name.trim().length < 2) { | |
| errors.push('Name is required and must be at least 2 characters'); | |
| } | |
| if (data.role && !['admin', 'user', 'moderator'].includes(data.role)) { | |
| errors.push('Role must be admin, user, or moderator'); | |
| } | |
| return { valid: errors.length === 0, errors }; | |
| } | |
| /** | |
| * Updates user properties. | |
| * @param {Object} updates - Properties to update | |
| */ | |
| update(updates) { | |
| const allowedFields = ['name', 'email', 'role', 'isActive']; | |
| for (const [key, value] of Object.entries(updates)) { | |
| if (allowedFields.includes(key)) { | |
| this[key] = value; | |
| } | |
| } | |
| this.updatedAt = new Date(); | |
| } | |
| /** | |
| * Checks if user has a specific role. | |
| * @param {string} role - Role to check | |
| * @returns {boolean} True if user has role | |
| */ | |
| hasRole(role) { | |
| const roleHierarchy = { user: 0, moderator: 1, admin: 2 }; | |
| return (roleHierarchy[this.role] ?? 0) >= (roleHierarchy[role] ?? 0); | |
| } | |
| /** | |
| * Serializes user to JSON-safe object. | |
| * @returns {Object} Serialized user | |
| */ | |
| toJSON() { | |
| return { | |
| id: this.id, | |
| email: this.email, | |
| name: this.name, | |
| role: this.role, | |
| isActive: this.isActive, | |
| createdAt: this.createdAt.toISOString(), | |
| updatedAt: this.updatedAt.toISOString(), | |
| }; | |
| } | |
| } | |
| export default User; | |
| `; | |
| } | |
| _generateModel() { | |
| return `/** | |
| * Authentication middleware with token validation. | |
| */ | |
| const TOKEN_EXPIRY = 3600000; // 1 hour | |
| /** | |
| * Creates an authentication middleware. | |
| * @param {Object} options - Middleware options | |
| * @returns {Function} Auth middleware | |
| */ | |
| export function createAuthMiddleware(options = {}) { | |
| const secret = options.secret || process.env.JWT_SECRET; | |
| const excludedPaths = options.excludedPaths || ['/health', '/api/public']; | |
| if (!secret) { | |
| throw new Error('JWT_SECRET is required for auth middleware'); | |
| } | |
| return async function authMiddleware(req, res, next) { | |
| if (excludedPaths.some(path => req.url.startsWith(path))) { | |
| return next(); | |
| } | |
| const authHeader = req.headers.authorization; | |
| if (!authHeader || !authHeader.startsWith('Bearer ')) { | |
| return res.writeHead(401, { 'Content-Type': 'application/json' }) | |
| .end(JSON.stringify({ error: 'Missing or invalid authorization header' })); | |
| } | |
| const token = authHeader.slice(7); | |
| try { | |
| const payload = await verifyToken(token, secret); | |
| if (payload.exp && payload.exp < Date.now()) { | |
| throw new Error('Token expired'); | |
| } | |
| req.user = payload; | |
| next(); | |
| } catch (error) { | |
| return res.writeHead(401, { 'Content-Type': 'application/json' }) | |
| .end(JSON.stringify({ error: 'Invalid or expired token' })); | |
| } | |
| }; | |
| } | |
| /** | |
| * Verifies a JWT token (simplified implementation). | |
| * @param {string} token - JWT token | |
| * @param {string} secret - Signing secret | |
| * @returns {Object} Token payload | |
| */ | |
| async function verifyToken(token, secret) { | |
| const parts = token.split('.'); | |
| if (parts.length !== 3) { | |
| throw new Error('Invalid token format'); | |
| } | |
| const payload = JSON.parse(Buffer.from(parts[1], 'base64').toString()); | |
| return payload; | |
| } | |
| /** | |
| * Checks if user has required permissions. | |
| * @param {string[]} requiredPermissions - Required permissions | |
| * @returns {Function} Permission check middleware | |
| */ | |
| export function requirePermissions(requiredPermissions) { | |
| return function (req, res, next) { | |
| if (!req.user) { | |
| return res.writeHead(401).end('Unauthorized'); | |
| } | |
| const userPermissions = req.user.permissions || []; | |
| const hasPermission = requiredPermissions.every(p => userPermissions.includes(p)); | |
| if (!hasPermission) { | |
| return res.writeHead(403).end('Insufficient permissions'); | |
| } | |
| next(); | |
| }; | |
| } | |
| `; | |
| } | |
| _generateAuthMiddleware() { | |
| return `/** | |
| * Centralized error handler for HTTP services. | |
| */ | |
| class AppError extends Error { | |
| constructor(message, statusCode = 500, code = 'INTERNAL_ERROR') { | |
| super(message); | |
| this.name = 'AppError'; | |
| this.statusCode = statusCode; | |
| this.code = code; | |
| this.isOperational = true; | |
| Error.captureStackTrace(this, this.constructor); | |
| } | |
| } | |
| /** | |
| * Creates an error handling middleware. | |
| * @returns {Function} Error handler | |
| */ | |
| export function createErrorHandler() { | |
| return function errorHandler(err, req, res, next) { | |
| if (err.name === 'AppError') { | |
| return sendError(res, err.statusCode, err.code, err.message); | |
| } | |
| if (err.name === 'ValidationError') { | |
| return sendError(res, 400, 'VALIDATION_ERROR', err.message); | |
| } | |
| if (err.name === 'UnauthorizedError') { | |
| return sendError(res, 401, 'UNAUTHORIZED', 'Authentication required'); | |
| } | |
| console.error('Unhandled error:', err); | |
| return sendError( | |
| res, | |
| 500, | |
| 'INTERNAL_ERROR', | |
| process.env.NODE_ENV === 'production' | |
| ? 'An unexpected error occurred' | |
| : err.message | |
| ); | |
| }; | |
| } | |
| /** | |
| * Wraps an async route handler to catch errors. | |
| * @param {Function} handler - Route handler | |
| * @returns {Function} Wrapped handler | |
| */ | |
| export function asyncHandler(handler) { | |
| return function (req, res, next) { | |
| Promise.resolve(handler(req, res, next)).catch(next); | |
| }; | |
| } | |
| function sendError(res, statusCode, code, message) { | |
| res.writeHead(statusCode, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ | |
| success: false, | |
| error: { code, message }, | |
| })); | |
| } | |
| export { AppError }; | |
| `; | |
| } | |
| _generateErrorHandler() { | |
| return `/** | |
| * Rate limiter using sliding window algorithm. | |
| */ | |
| class RateLimiter { | |
| constructor(options = {}) { | |
| this.windowMs = options.windowMs || 60000; // 1 minute | |
| this.maxRequests = options.maxRequests || 100; | |
| this._requests = new Map(); | |
| } | |
| /** | |
| * Checks if a request is allowed. | |
| * @param {string} key - Identifier (IP, user ID, etc.) | |
| * @returns {{allowed: boolean, remaining: number, resetAt: number}} | |
| */ | |
| isAllowed(key) { | |
| const now = Date.now(); | |
| const windowStart = now - this.windowMs; | |
| if (!this._requests.has(key)) { | |
| this._requests.set(key, []); | |
| } | |
| const timestamps = this._requests.get(key); | |
| // Remove expired timestamps | |
| while (timestamps.length > 0 && timestamps[0] <= windowStart) { | |
| timestamps.shift(); | |
| } | |
| const remaining = this.maxRequests - timestamps.length; | |
| if (remaining <= 0) { | |
| return { | |
| allowed: false, | |
| remaining: 0, | |
| resetAt: timestamps[0] + this.windowMs, | |
| }; | |
| } | |
| timestamps.push(now); | |
| return { | |
| allowed: true, | |
| remaining: remaining - 1, | |
| resetAt: now + this.windowMs, | |
| }; | |
| } | |
| /** | |
| * Creates middleware function for HTTP servers. | |
| * @param {Function} [keyFn] - Function to extract key from request | |
| * @returns {Function} Rate limit middleware | |
| */ | |
| middleware(keyFn = (req) => req.ip || req.socket.remoteAddress) { | |
| return (req, res, next) => { | |
| const key = keyFn(req); | |
| const result = this.isAllowed(key); | |
| res.setHeader('X-RateLimit-Limit', this.maxRequests); | |
| res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining)); | |
| res.setHeader('X-RateLimit-Reset', result.resetAt); | |
| if (!result.allowed) { | |
| res.writeHead(429, { 'Content-Type': 'application/json' }); | |
| return res.end(JSON.stringify({ | |
| error: 'Too Many Requests', | |
| retryAfter: Math.ceil((result.resetAt - Date.now()) / 1000), | |
| })); | |
| } | |
| next(); | |
| }; | |
| } | |
| /** | |
| * Resets rate limit for a specific key. | |
| * @param {string} key - Identifier to reset | |
| */ | |
| reset(key) { | |
| this._requests.delete(key); | |
| } | |
| /** | |
| * Clears all rate limit data. | |
| */ | |
| clear() { | |
| this._requests.clear(); | |
| } | |
| } | |
| export default RateLimiter; | |
| `; | |
| } | |
| _generateRateLimiter() { | |
| return `/** | |
| * Rate limiter using sliding window algorithm. | |
| */ | |
| class RateLimiter { | |
| constructor(options = {}) { | |
| this.windowMs = options.windowMs || 60000; | |
| this.maxRequests = options.maxRequests || 100; | |
| this._requests = new Map(); | |
| } | |
| isAllowed(key) { | |
| const now = Date.now(); | |
| const windowStart = now - this.windowMs; | |
| if (!this._requests.has(key)) { | |
| this._requests.set(key, []); | |
| } | |
| const timestamps = this._requests.get(key); | |
| while (timestamps.length > 0 && timestamps[0] <= windowStart) { | |
| timestamps.shift(); | |
| } | |
| const remaining = this.maxRequests - timestamps.length; | |
| if (remaining <= 0) { | |
| return { | |
| allowed: false, | |
| remaining: 0, | |
| resetAt: timestamps[0] + this.windowMs, | |
| }; | |
| } | |
| timestamps.push(now); | |
| return { | |
| allowed: true, | |
| remaining: remaining - 1, | |
| resetAt: now + this.windowMs, | |
| }; | |
| } | |
| middleware(keyFn = (req) => req.ip) { | |
| return (req, res, next) => { | |
| const key = keyFn(req); | |
| const result = this.isAllowed(key); | |
| res.setHeader('X-RateLimit-Limit', this.maxRequests); | |
| res.setHeader('X-RateLimit-Remaining', Math.max(0, result.remaining)); | |
| if (!result.allowed) { | |
| res.writeHead(429, { 'Content-Type': 'application/json' }); | |
| return res.end(JSON.stringify({ error: 'Too Many Requests' })); | |
| } | |
| next(); | |
| }; | |
| } | |
| reset(key) { | |
| this._requests.delete(key); | |
| } | |
| clear() { | |
| this._requests.clear(); | |
| } | |
| } | |
| export default RateLimiter; | |
| `; | |
| } | |
| _injectBug(content) { | |
| const bugTypes = [ | |
| () => content.replace(/===/g, '==').replace(/!==/g, '!='), | |
| () => content.replace(/return null;/g, 'return undefined;'), | |
| () => content.replace(/const /g, 'let ').split('\n').slice(0, -3).join('\n') + '\n' + content.split('\n').slice(-3).join('\n'), | |
| () => content.replace(/\.length/g, '.lenght'), | |
| () => content.replace(/JSON.parse/g, 'JSON.parase'), | |
| ]; | |
| return sample(bugTypes)(); | |
| } | |
| _injectTypo(content) { | |
| const typoTypes = [ | |
| () => content.replace(/function /g, 'funciton '), | |
| () => content.replace(/parameter/g, 'paramter'), | |
| () => content.replace(/callback/g, 'callbak'), | |
| () => content.replace(/response/g, 'resposne'), | |
| () => content.replace(/undefined/g, 'undefiend'), | |
| ]; | |
| return sample(typoTypes)(); | |
| } | |
| generateTestFile(filePath, codeContent) { | |
| const fileName = filePath.split('/').pop().replace('.js', ''); | |
| return `import { describe, it, expect } from '@jest/globals'; | |
| import * as module from '../${fileName}.js'; | |
| describe('${fileName}', () => { | |
| it('should export expected functions', () => { | |
| const exports = Object.keys(module); | |
| expect(exports.length).toBeGreaterThan(0); | |
| }); | |
| it('should handle edge cases', () => { | |
| expect(() => module).not.toThrow(); | |
| }); | |
| }); | |
| `; | |
| } | |
| generateCommitMessageForFile(filePath, fileType) { | |
| const messages = { | |
| utility: [ | |
| `feat: add ${filePath.split('/').pop().replace('.js', '')} utility functions`, | |
| `feat(utils): implement reusable helper functions`, | |
| ], | |
| validator: [ | |
| `feat: add input validation utilities`, | |
| `feat(validation): add data validation helpers`, | |
| ], | |
| formatter: [ | |
| `feat: add formatting and transformation utilities`, | |
| `feat(formatting): implement data formatters`, | |
| ], | |
| api: [ | |
| `feat: implement API service with caching`, | |
| `feat(api): add HTTP client with request queuing`, | |
| ], | |
| cache: [ | |
| `feat: add in-memory cache with TTL support`, | |
| `feat(cache): implement LRU cache implementation`, | |
| ], | |
| logger: [ | |
| `feat: add structured logging utility`, | |
| `feat(logging): implement leveled logger`, | |
| ], | |
| model: [ | |
| `feat: add data model with validation`, | |
| `feat(model): implement ${filePath.split('/').pop().replace('.js', '')} entity`, | |
| ], | |
| auth: [ | |
| `feat: add authentication middleware`, | |
| `feat(auth): implement token-based auth`, | |
| ], | |
| error: [ | |
| `feat: add centralized error handling`, | |
| `feat(errors): implement error handler middleware`, | |
| ], | |
| rateLimiter: [ | |
| `feat: add rate limiting middleware`, | |
| `feat(rate-limit): implement sliding window rate limiter`, | |
| ], | |
| }; | |
| return sample(messages[fileType] || messages.utility); | |
| } | |
| } | |
| export default CodeGenerator; | |