activity-simulator / src /generators /codeGenerator.js
abedelbahnasy55's picture
feat: cloud simulator - Docker, dashboard, auto-start
ccb6b75
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;