diff --git a/01-deep-clone/index.js b/01-deep-clone/index.js index e0281df..a29b047 100644 --- a/01-deep-clone/index.js +++ b/01-deep-clone/index.js @@ -9,33 +9,65 @@ * @returns {*} A deep clone of the input value */ function deepClone(value, visited = new WeakMap()) { - // TODO: Implement deep cloning - // Step 1: Handle primitives (return as-is) - // Primitives: null, undefined, number, string, boolean, symbol, bigint + if (value === null || typeof value !== 'object') { + return value; + } // Step 2: Check for circular references using the visited WeakMap - // If we've seen this object before, return the cached clone + if (visited.has(value)) { + return visited.get(value); + } // Step 3: Handle Date objects - // Create a new Date with the same time value + if (value instanceof Date) { + return new Date(value.getTime()); + } // Step 4: Handle RegExp objects - // Create a new RegExp with the same source and flags + if (value instanceof RegExp) { + return new RegExp(value.source, value.flags); + } // Step 5: Handle Map objects - // Create a new Map and deep clone each key-value pair + if (value instanceof Map) { + const clonedMap = new Map(); + visited.set(value, clonedMap); + for (const [key, val] of value) { + clonedMap.set(deepClone(key, visited), deepClone(val, visited)); + } + return clonedMap; + } // Step 6: Handle Set objects - // Create a new Set and deep clone each value + if (value instanceof Set) { + const clonedSet = new Set(); + visited.set(value, clonedSet); + for (const val of value) { + clonedSet.add(deepClone(val, visited)); + } + return clonedSet; + } // Step 7: Handle Arrays - // Create a new array and deep clone each element + if (Array.isArray(value)) { + const clonedArray = []; + visited.set(value, clonedArray); + for (let i = 0; i < value.length; i++) { + clonedArray[i] = deepClone(value[i], visited); + } + return clonedArray; + } // Step 8: Handle plain Objects - // Create a new object and deep clone each property - - return undefined; // Broken: Replace with your implementation + const clonedObj = {}; + visited.set(value, clonedObj); + for (const key in value) { + if (Object.prototype.hasOwnProperty.call(value, key)) { + clonedObj[key] = deepClone(value[key], visited); + } + } + return clonedObj; } module.exports = { deepClone }; diff --git a/02-debounce-throttle/index.js b/02-debounce-throttle/index.js index 397281b..cff51b6 100644 --- a/02-debounce-throttle/index.js +++ b/02-debounce-throttle/index.js @@ -9,21 +9,21 @@ * @returns {Function} The debounced function with a cancel() method */ function debounce(fn, delay) { - // TODO: Implement debounce + let timeoutId = null; - // Step 1: Create a variable to store the timeout ID + function debounced(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + fn.apply(this, args); + }, delay); + } - // Step 2: Create the debounced function that: - // - Clears any existing timeout - // - Sets a new timeout to call fn after delay - // - Preserves `this` context and arguments + debounced.cancel = function () { + clearTimeout(timeoutId); + timeoutId = null; + }; - // Step 3: Add a cancel() method to clear pending timeout - - // Step 4: Return the debounced function - - // Return a placeholder that doesn't work - throw new Error("Not implemented"); + return debounced; } /** @@ -37,23 +37,26 @@ function debounce(fn, delay) { * @returns {Function} The throttled function with a cancel() method */ function throttle(fn, limit) { - // TODO: Implement throttle - - // Step 1: Create variables to track: - // - Whether we're currently in a throttle period - // - The timeout ID for cleanup - - // Step 2: Create the throttled function that: - // - If not throttling, execute fn immediately and start throttle period - // - If throttling, ignore the call - // - Preserves `this` context and arguments - - // Step 3: Add a cancel() method to reset throttle state - - // Step 4: Return the throttled function - - // Return a placeholder that doesn't work - throw new Error("Not implemented"); + let isThrottling = false; + let timeoutId = null; + + function throttled(...args) { + if (!isThrottling) { + fn.apply(this, args); + isThrottling = true; + timeoutId = setTimeout(() => { + isThrottling = false; + }, limit); + } + } + + throttled.cancel = function () { + clearTimeout(timeoutId); + isThrottling = false; + timeoutId = null; + }; + + return throttled; } module.exports = { debounce, throttle }; diff --git a/03-custom-bind/index.js b/03-custom-bind/index.js index 3e691f9..bf4ccaa 100644 --- a/03-custom-bind/index.js +++ b/03-custom-bind/index.js @@ -11,29 +11,27 @@ * @returns {Function} A new bound function */ function customBind(fn, context, ...boundArgs) { - // TODO: Implement custom bind + if (typeof fn !== "function") { + throw new TypeError("customBind requires a function"); + } - // Step 1: Validate that fn is a function - // Throw TypeError if not + function boundFunction(...args) { + const allArgs = [...boundArgs, ...args]; - // Step 2: Create the bound function - // It should: - // - Combine boundArgs with any new arguments - // - Call the original function with the combined arguments - // - Use the correct `this` context + // Check if called as constructor (with new) + if (new.target) { + return fn.apply(this, allArgs); + } - // Step 3: Handle constructor calls (when used with `new`) - // When called as a constructor: - // - `this` should be a new instance, not the bound context - // - The prototype chain should be preserved + return fn.apply(context, allArgs); + } - // Step 4: Preserve the prototype for constructor usage - // boundFunction.prototype = Object.create(fn.prototype) + // Preserve prototype for constructor usage + if (fn.prototype) { + boundFunction.prototype = Object.create(fn.prototype); + } - // Step 5: Return the bound function - - // Return placeholder that doesn't work - throw new Error("Not implemented"); + return boundFunction; } /** diff --git a/04-memoization/index.js b/04-memoization/index.js index dc0f09b..5f8b08e 100644 --- a/04-memoization/index.js +++ b/04-memoization/index.js @@ -11,46 +11,50 @@ * @returns {Function} Memoized function with cache control methods */ function memoize(fn, options = {}) { - // TODO: Implement memoization + const { maxSize, ttl, keyGenerator } = options; + const cache = new Map(); - // Step 1: Extract options with defaults - // const { maxSize, ttl, keyGenerator } = options; + const defaultKeyGenerator = (args) => JSON.stringify(args); + const getKey = keyGenerator || defaultKeyGenerator; - // Step 2: Create the cache (use Map for ordered keys) - // const cache = new Map(); + function memoized(...args) { + const key = getKey(args); - // Step 3: Create default key generator - // Default: JSON.stringify(args) or args.join(',') + if (cache.has(key)) { + const entry = cache.get(key); - // Step 4: Create the memoized function - // - Generate cache key from arguments - // - Check if key exists and is not expired (TTL) - // - If cached, return cached value - // - If not cached, call fn and store result - // - Handle maxSize eviction (remove oldest) + // Check TTL expiration + if (ttl && Date.now() - entry.timestamp > ttl) { + cache.delete(key); + } else { + return entry.value; + } + } - // Step 5: Add cache control methods - // memoized.cache = { - // clear: () => cache.clear(), - // delete: (key) => cache.delete(key), - // has: (key) => cache.has(key), - // get size() { return cache.size; } - // }; + // Call original function + const result = fn.apply(this, args); - // Step 6: Return memoized function + // Evict oldest if maxSize reached + if (maxSize && cache.size >= maxSize) { + const oldestKey = cache.keys().next().value; + cache.delete(oldestKey); + } + + // Store result with timestamp + cache.set(key, { value: result, timestamp: Date.now() }); + + return result; + } - // Return placeholder that doesn't work - const memoized = function () { - return undefined; - }; memoized.cache = { - clear: () => {}, - delete: () => false, - has: () => false, + clear: () => cache.clear(), + delete: (key) => cache.delete(key), + has: (key) => cache.has(key), get size() { - return -1; + return cache.size; }, }; + return memoized; } diff --git a/05-promise-utilities/index.js b/05-promise-utilities/index.js index e59a8eb..d717a38 100644 --- a/05-promise-utilities/index.js +++ b/05-promise-utilities/index.js @@ -8,30 +8,28 @@ * @returns {Promise} A promise that resolves to an array of results */ function promiseAll(promises) { - // TODO: Implement promiseAll - - // Step 1: Convert iterable to array - // const promiseArray = Array.from(promises); - - // Step 2: Handle empty array case - // Return Promise.resolve([]) for empty input - - // Step 3: Create a new Promise - // return new Promise((resolve, reject) => { - - // Step 4: Track results and completion count - // const results = new Array(promiseArray.length); - // let completed = 0; - - // Step 5: Iterate and handle each promise - // - Use Promise.resolve() to handle non-promise values - // - On resolve: store result at correct index, increment count - // - If all completed: resolve with results array - // - On reject: immediately reject the whole promise - - // }); - - return Promise.reject(new Error("Not implemented")); // Broken: Replace with your implementation + const promiseArray = Array.from(promises); + + if (promiseArray.length === 0) { + return Promise.resolve([]); + } + + return new Promise((resolve, reject) => { + const results = new Array(promiseArray.length); + let completed = 0; + + promiseArray.forEach((promise, index) => { + Promise.resolve(promise) + .then((value) => { + results[index] = value; + completed++; + if (completed === promiseArray.length) { + resolve(results); + } + }) + .catch(reject); + }); + }); } /** @@ -43,19 +41,17 @@ function promiseAll(promises) { * @returns {Promise} A promise that settles with the first result */ function promiseRace(promises) { - // TODO: Implement promiseRace - - // Step 1: Convert iterable to array - - // Step 2: Handle empty array (return pending promise) - // For empty array, return a promise that never settles + const promiseArray = Array.from(promises); - // Step 3: Create a new Promise - // The first promise to settle wins + if (promiseArray.length === 0) { + return new Promise(() => {}); // Never settles + } - // Step 4: For each promise, attach then/catch that resolves/rejects the race - - return new Promise(() => {}); // Replace with your implementation + return new Promise((resolve, reject) => { + promiseArray.forEach((promise) => { + Promise.resolve(promise).then(resolve).catch(reject); + }); + }); } /** @@ -68,24 +64,32 @@ function promiseRace(promises) { * @returns {Promise} A promise that resolves to an array of settlement objects */ function promiseAllSettled(promises) { - // TODO: Implement promiseAllSettled - - // Step 1: Convert iterable to array - - // Step 2: Handle empty array case - - // Step 3: Create a new Promise - - // Step 4: Track results and completion count - // Each result is: { status: 'fulfilled', value } or { status: 'rejected', reason } - - // Step 5: For each promise: - // - On resolve: store { status: 'fulfilled', value } - // - On reject: store { status: 'rejected', reason } - // - Never reject the outer promise - // - Resolve when all have settled - - return Promise.reject(new Error("Not implemented")); // Broken: Replace with your implementation + const promiseArray = Array.from(promises); + + if (promiseArray.length === 0) { + return Promise.resolve([]); + } + + return new Promise((resolve) => { + const results = new Array(promiseArray.length); + let settled = 0; + + promiseArray.forEach((promise, index) => { + Promise.resolve(promise) + .then((value) => { + results[index] = { status: "fulfilled", value }; + }) + .catch((reason) => { + results[index] = { status: "rejected", reason }; + }) + .finally(() => { + settled++; + if (settled === promiseArray.length) { + resolve(results); + } + }); + }); + }); } /** @@ -98,27 +102,30 @@ function promiseAllSettled(promises) { * @returns {Promise} A promise that resolves with the first fulfilled value */ function promiseAny(promises) { - // TODO: Implement promiseAny - - // Step 1: Convert iterable to array - - // Step 2: Handle empty array (reject with AggregateError) - - // Step 3: Create a new Promise - - // Step 4: Track rejection count and errors - // const errors = []; - // let rejectedCount = 0; - - // Step 5: For each promise: - // - On resolve: immediately resolve the outer promise (first wins) - // - On reject: collect error, increment count - // - If all rejected: reject with AggregateError - - // Note: AggregateError is created like: - // new AggregateError(errorsArray, 'All promises were rejected') - - return Promise.reject(new AggregateError([], "No promises")); // Replace + const promiseArray = Array.from(promises); + + if (promiseArray.length === 0) { + return Promise.reject( + new AggregateError([], "All promises were rejected"), + ); + } + + return new Promise((resolve, reject) => { + const errors = new Array(promiseArray.length); + let rejectedCount = 0; + + promiseArray.forEach((promise, index) => { + Promise.resolve(promise) + .then(resolve) + .catch((reason) => { + errors[index] = reason; + rejectedCount++; + if (rejectedCount === promiseArray.length) { + reject(new AggregateError(errors, "All promises were rejected")); + } + }); + }); + }); } module.exports = { promiseAll, promiseRace, promiseAllSettled, promiseAny }; diff --git a/06-async-queue/index.js b/06-async-queue/index.js index 2ab4d0a..7d8be88 100644 --- a/06-async-queue/index.js +++ b/06-async-queue/index.js @@ -4,132 +4,95 @@ * A queue that processes async tasks with concurrency control. */ class AsyncQueue { - /** - * Create an async queue - * @param {Object} options - Queue options - * @param {number} [options.concurrency=1] - Maximum concurrent tasks - * @param {boolean} [options.autoStart=true] - Start processing immediately - */ constructor(options = {}) { - // TODO: Initialize the queue - // Step 1: Extract options with defaults - // this.concurrency = options.concurrency || 1; - // this.autoStart = options.autoStart !== false; - // Step 2: Initialize internal state - // this.queue = []; // Pending tasks - // this.running = 0; // Currently running count - // this.paused = false; // Paused state - // this.emptyCallbacks = []; // Callbacks for empty event + this.concurrency = options.concurrency || 1; + this.autoStart = options.autoStart !== false; + this.queue = []; + this.running = 0; + this.paused = !this.autoStart; + this.emptyCallbacks = []; } - /** - * Add a task to the queue - * @param {Function} task - Async function to execute - * @param {Object} [options] - Task options - * @param {number} [options.priority=0] - Task priority (higher = sooner) - * @returns {Promise} Resolves when task completes - */ add(task, options = {}) { - // TODO: Implement add - - // Step 1: Create a new Promise and store its resolve/reject - - // Step 2: Create task entry with: task, priority, resolve, reject - - // Step 3: Add to queue (consider priority ordering) - - // Step 4: Try to process if autoStart and not paused - - // Step 5: Return the promise - - return Promise.resolve(); // Replace with your implementation + const priority = options.priority || 0; + + return new Promise((resolve, reject) => { + const entry = { task, priority, resolve, reject }; + + // Insert in priority order (higher priority first) + let inserted = false; + for (let i = 0; i < this.queue.length; i++) { + if (priority > this.queue[i].priority) { + this.queue.splice(i, 0, entry); + inserted = true; + break; + } + } + if (!inserted) { + this.queue.push(entry); + } + + this._process(); + }); } - /** - * Start processing the queue - */ start() { - // TODO: Implement start - // Set paused to false and trigger processing + this.paused = false; + this._process(); } - /** - * Pause the queue (running tasks will complete) - */ pause() { - // TODO: Implement pause - // Set paused to true + this.paused = true; } - /** - * Clear all pending tasks - */ clear() { - // TODO: Implement clear - // Empty the queue array - // Optionally: reject pending promises with an error + this.queue = []; } - /** - * Register callback for when queue becomes empty - * @param {Function} callback - Called when queue is empty - */ onEmpty(callback) { - // TODO: Implement onEmpty - // Store callback to be called when size becomes 0 and nothing running + this.emptyCallbacks.push(callback); } - /** - * Number of pending tasks - * @returns {number} - */ get size() { - // TODO: Return queue length - throw new Error("Not implemented"); + return this.queue.length; } - /** - * Number of currently running tasks - * @returns {number} - */ get pending() { - // TODO: Return running count - throw new Error("Not implemented"); + return this.running; } - /** - * Whether queue is paused - * @returns {boolean} - */ get isPaused() { - // TODO: Return paused state - throw new Error("Not implemented"); + return this.paused; } - /** - * Internal: Process next tasks from queue - * @private - */ _process() { - // TODO: Implement _process - // Step 1: Check if we can run more tasks - // - Not paused - // - Running count < concurrency - // - Queue has items - // Step 2: Take task from queue (respect priority) - // Step 3: Increment running count - // Step 4: Execute task and handle result - // - On success: resolve the task's promise - // - On error: reject the task's promise - // - Always: decrement running, call _process again, check if empty + while (!this.paused && this.running < this.concurrency && this.queue.length > 0) { + const entry = this.queue.shift(); + this.running++; + + Promise.resolve() + .then(() => entry.task()) + .then( + (result) => { + this.running--; + entry.resolve(result); + this._process(); + this._checkEmpty(); + }, + (error) => { + this.running--; + entry.reject(error); + this._process(); + this._checkEmpty(); + }, + ); + } } - /** - * Internal: Check and trigger empty callbacks - * @private - */ _checkEmpty() { - // TODO: If queue is empty and nothing running, call empty callbacks + if (this.queue.length === 0 && this.running === 0) { + this.emptyCallbacks.forEach((cb) => cb()); + } } } diff --git a/07-retry-with-backoff/index.js b/07-retry-with-backoff/index.js index ed7fd75..cc89375 100644 --- a/07-retry-with-backoff/index.js +++ b/07-retry-with-backoff/index.js @@ -14,39 +14,65 @@ * @param {Function} [options.onRetry] - Called before each retry * @returns {Promise} Result of fn or throws last error */ -async function retry(fn, options = {}) { - // TODO: Implement retry with backoff - - // Step 1: Extract options with defaults - // const { - // maxRetries = 3, - // initialDelay = 1000, - // maxDelay = 30000, - // backoff = 'exponential', - // jitter = false, - // retryIf = () => true, - // onRetry = () => {} - // } = options; - - // Step 2: Initialize attempt counter and last error - - // Step 3: Loop up to maxRetries + 1 (initial attempt + retries) - - // Step 4: Try to execute fn - // - On success: return result - // - On error: check if should retry - - // Step 5: If should retry: - // - Call onRetry callback - // - Calculate delay based on backoff strategy - // - Apply maxDelay cap - // - Apply jitter if enabled - // - Wait for delay - // - Continue to next attempt - - // Step 6: If all retries exhausted, throw last error - - throw new Error("Not implemented"); // Replace with your implementation +function retry(fn, options = {}) { + const { + maxRetries = 3, + initialDelay = 1000, + maxDelay = 30000, + backoff = 'exponential', + jitter = false, + retryIf = () => true, + onRetry = () => {} + } = options; + + return new Promise((resolve, reject) => { + let attempt = 0; + + const handleError = (error) => { + const isLastAttempt = attempt >= maxRetries; + const shouldRetry = retryIf(error); + + if (isLastAttempt || !shouldRetry) { + reject(error); + return; + } + + // Call onRetry callback before waiting + onRetry(error, attempt + 1); + + // Calculate delay + let delay = calculateDelay(backoff, attempt + 1, initialDelay); + + // Apply maxDelay cap + delay = Math.min(delay, maxDelay); + + // Apply jitter if enabled + if (jitter) { + delay = applyJitter(delay); + } + + attempt++; + + // Wait before next attempt + setTimeout(tryExecute, delay); + }; + + const tryExecute = () => { + let result; + try { + result = fn(); + } catch (error) { + handleError(error); + return; + } + + // Handle both sync and async results + Promise.resolve(result).then(resolve, handleError); + }; + + // Start execution immediately + tryExecute(); + }); } /** @@ -58,13 +84,16 @@ async function retry(fn, options = {}) { * @returns {number} Calculated delay in ms */ function calculateDelay(strategy, attempt, initialDelay) { - // TODO: Implement delay calculation - - // Fixed: delay = initialDelay - // Linear: delay = initialDelay * attempt - // Exponential: delay = initialDelay * 2^(attempt-1) - - throw new Error("Not implemented"); + switch (strategy) { + case 'fixed': + return initialDelay; + case 'linear': + return initialDelay * attempt; + case 'exponential': + return initialDelay * Math.pow(2, attempt - 1); + default: + return initialDelay; + } } /** @@ -74,10 +103,7 @@ function calculateDelay(strategy, attempt, initialDelay) { * @returns {number} Delay with random jitter (0-25% added) */ function applyJitter(delay) { - // TODO: Add 0-25% random jitter - // return delay * (1 + Math.random() * 0.25); - - throw new Error("Not implemented"); + return delay * (1 + Math.random() * 0.25); } /** diff --git a/07-retry-with-backoff/index.test.js b/07-retry-with-backoff/index.test.js index a4ca6c8..bd5af61 100644 --- a/07-retry-with-backoff/index.test.js +++ b/07-retry-with-backoff/index.test.js @@ -45,12 +45,13 @@ describe("retry", () => { const fn = jest.fn().mockRejectedValue(error); const promise = retry(fn, { maxRetries: 2, initialDelay: 100 }); + const expectation = expect(promise).rejects.toThrow("persistent failure"); await jest.advanceTimersByTimeAsync(0); // attempt 1 await jest.advanceTimersByTimeAsync(100); // attempt 2 await jest.advanceTimersByTimeAsync(200); // attempt 3 - await expect(promise).rejects.toThrow("persistent failure"); + await expectation; expect(fn).toHaveBeenCalledTimes(3); // initial + 2 retries }); }); @@ -60,12 +61,13 @@ describe("retry", () => { const fn = jest.fn().mockRejectedValue(new Error("fail")); const promise = retry(fn, { maxRetries: 5, initialDelay: 10 }); + const expectation = expect(promise).rejects.toThrow(); for (let i = 0; i < 10; i++) { await jest.advanceTimersByTimeAsync(100); } - await expect(promise).rejects.toThrow(); + await expectation; expect(fn).toHaveBeenCalledTimes(6); // initial + 5 retries }); @@ -73,9 +75,11 @@ describe("retry", () => { const fn = jest.fn().mockRejectedValue(new Error("fail")); const promise = retry(fn, { maxRetries: 0 }); + const expectation = expect(promise).rejects.toThrow(); + await jest.advanceTimersByTimeAsync(0); - await expect(promise).rejects.toThrow(); + await expectation; expect(fn).toHaveBeenCalledTimes(1); }); }); @@ -105,9 +109,10 @@ describe("retry", () => { const promise = retry(fn, { retryIf: (error) => error.status >= 500, }); + const expectation = expect(promise).rejects.toEqual({ status: 400 }); await jest.advanceTimersByTimeAsync(0); - await expect(promise).rejects.toEqual({ status: 400 }); + await expectation; expect(fn).toHaveBeenCalledTimes(1); }); }); @@ -198,6 +203,7 @@ describe("retry with backoff strategies", () => { maxDelay: 5000, backoff: "exponential", }); + const expectation = expect(promise).rejects.toThrow(); // Track delays by advancing time // exponential would be: 1000, 2000, 4000, 8000, 16000 @@ -207,6 +213,6 @@ describe("retry with backoff strategies", () => { await jest.advanceTimersByTimeAsync(5000); } - await expect(promise).rejects.toThrow(); + await expectation; }); }); diff --git a/08-event-emitter/index.js b/08-event-emitter/index.js index b7c9a7c..299631b 100644 --- a/08-event-emitter/index.js +++ b/08-event-emitter/index.js @@ -5,8 +5,7 @@ */ class EventEmitter { constructor() { - // TODO: Initialize event storage - // this.events = new Map(); // or {} + this.events = new Map(); } /** @@ -16,15 +15,11 @@ class EventEmitter { * @returns {EventEmitter} this (for chaining) */ on(event, listener) { - // TODO: Implement on - - // Step 1: Get or create the listeners array for this event - - // Step 2: Add the listener to the array - - // Step 3: Return this for chaining - - return null; // Broken: should return this + if (!this.events.has(event)) { + this.events.set(event, []); + } + this.events.get(event).push(listener); + return this; } /** @@ -34,16 +29,20 @@ class EventEmitter { * @returns {EventEmitter} this (for chaining) */ off(event, listener) { - // TODO: Implement off + if (!this.events.has(event)) { + return this; + } - // Step 1: Get the listeners array for this event + const listeners = this.events.get(event); + const index = listeners.findIndex( + (l) => l === listener || l.listener === listener + ); - // Step 2: Find and remove the listener - // Note: Handle wrapped 'once' listeners + if (index !== -1) { + listeners.splice(index, 1); + } - // Step 3: Return this for chaining - - return null; // Broken: should return this + return this; } /** @@ -53,18 +52,14 @@ class EventEmitter { * @returns {boolean} true if event had listeners */ emit(event, ...args) { - // TODO: Implement emit - - // Step 1: Get the listeners array for this event - - // Step 2: If no listeners, return false - - // Step 3: Call each listener with the arguments - // Make a copy of the array to handle removals during emit + if (!this.events.has(event) || this.events.get(event).length === 0) { + return false; + } - // Step 4: Return true + const listeners = [...this.events.get(event)]; + listeners.forEach((listener) => listener(...args)); - throw new Error("Not implemented"); + return true; } /** @@ -74,19 +69,15 @@ class EventEmitter { * @returns {EventEmitter} this (for chaining) */ once(event, listener) { - // TODO: Implement once + const wrapper = (...args) => { + this.off(event, wrapper); + listener(...args); + }; - // Step 1: Create a wrapper function that: - // - Removes itself after being called - // - Calls the original listener with arguments + wrapper.listener = listener; + this.on(event, wrapper); - // Step 2: Store reference to original listener for 'off' to work - - // Step 3: Register the wrapper with 'on' - - // Step 4: Return this for chaining - - return null; // Broken: should return this + return this; } /** @@ -95,12 +86,13 @@ class EventEmitter { * @returns {EventEmitter} this (for chaining) */ removeAllListeners(event) { - // TODO: Implement removeAllListeners - - // If event is provided, remove only that event's listeners - // If no event, clear all events + if (event) { + this.events.delete(event); + } else { + this.events.clear(); + } - return null; // Broken: should return this + return this; } /** @@ -109,11 +101,10 @@ class EventEmitter { * @returns {Function[]} Array of listener functions */ listeners(event) { - // TODO: Implement listeners - - // Return copy of listeners array, or empty array if none - - throw new Error("Not implemented"); + if (!this.events.has(event)) { + return []; + } + return [...this.events.get(event)]; } /** @@ -122,9 +113,10 @@ class EventEmitter { * @returns {number} Listener count */ listenerCount(event) { - // TODO: Implement listenerCount - - throw new Error("Not implemented"); + if (!this.events.has(event)) { + return 0; + } + return this.events.get(event).length; } } diff --git a/09-observable/index.js b/09-observable/index.js index a7e8f15..c15fa16 100644 --- a/09-observable/index.js +++ b/09-observable/index.js @@ -9,8 +9,7 @@ class Observable { * @param {Function} subscribeFn - Function called with subscriber on subscribe */ constructor(subscribeFn) { - // TODO: Store the subscribe function - // this._subscribeFn = subscribeFn; + this._subscribeFn = subscribeFn; } /** @@ -19,23 +18,50 @@ class Observable { * @returns {Object} Subscription with unsubscribe method */ subscribe(observer) { - // TODO: Implement subscribe - // Step 1: Normalize observer (handle function shorthand) - // If observer is a function, wrap it: { next: observer } - - // Step 2: Create a subscriber object that: - // - Has next, error, complete methods - // - Tracks if completed/errored (stops accepting values) - // - Calls observer methods when appropriate + const normalizedObserver = + typeof observer === "function" ? { next: observer } : observer; + + // Step 2: Create a subscriber object + let isActive = true; + let cleanupFn = null; + + const subscriber = { + next: (value) => { + if (isActive && normalizedObserver.next) { + normalizedObserver.next(value); + } + }, + error: (err) => { + if (isActive) { + isActive = false; + if (normalizedObserver.error) { + normalizedObserver.error(err); + } + } + }, + complete: () => { + if (isActive) { + isActive = false; + if (normalizedObserver.complete) { + normalizedObserver.complete(); + } + } + }, + }; // Step 3: Call the subscribe function with the subscriber - - // Step 4: Handle cleanup function returned by subscribeFn + cleanupFn = this._subscribeFn(subscriber); // Step 5: Return subscription object with unsubscribe method - - throw new Error("Not implemented"); + return { + unsubscribe: () => { + isActive = false; + if (cleanupFn && typeof cleanupFn === "function") { + cleanupFn(); + } + }, + }; } /** @@ -44,14 +70,15 @@ class Observable { * @returns {Observable} New Observable with transformed values */ map(fn) { - // TODO: Implement map operator - - // Return new Observable that: - // - Subscribes to source (this) - // - Calls fn on each value - // - Emits transformed value + return new Observable((subscriber) => { + const subscription = this.subscribe({ + next: (value) => subscriber.next(fn(value)), + error: (err) => subscriber.error(err), + complete: () => subscriber.complete(), + }); - return new Observable(() => {}); // Broken: Replace with implementation + return () => subscription.unsubscribe(); + }); } /** @@ -60,13 +87,19 @@ class Observable { * @returns {Observable} New Observable with filtered values */ filter(predicate) { - // TODO: Implement filter operator - - // Return new Observable that: - // - Subscribes to source (this) - // - Only emits values where predicate returns true - - return new Observable(() => {}); // Broken: Replace with implementation + return new Observable((subscriber) => { + const subscription = this.subscribe({ + next: (value) => { + if (predicate(value)) { + subscriber.next(value); + } + }, + error: (err) => subscriber.error(err), + complete: () => subscriber.complete(), + }); + + return () => subscription.unsubscribe(); + }); } /** @@ -75,14 +108,31 @@ class Observable { * @returns {Observable} New Observable limited to count values */ take(count) { - // TODO: Implement take operator - - // Return new Observable that: - // - Subscribes to source (this) - // - Emits first `count` values - // - Completes after `count` values - - return new Observable(() => {}); // Broken: Replace with implementation + return new Observable((subscriber) => { + let taken = 0; + let completed = false; + + const subscription = this.subscribe({ + next: (value) => { + if (!completed && taken < count) { + subscriber.next(value); + taken++; + if (taken === count) { + completed = true; + subscriber.complete(); + } + } + }, + error: (err) => subscriber.error(err), + complete: () => { + if (!completed) { + subscriber.complete(); + } + }, + }); + + return () => subscription.unsubscribe(); + }); } /** @@ -91,14 +141,23 @@ class Observable { * @returns {Observable} New Observable that skips first count values */ skip(count) { - // TODO: Implement skip operator - - // Return new Observable that: - // - Subscribes to source (this) - // - Ignores first `count` values - // - Emits remaining values - - return new Observable(() => {}); // Broken: Replace with implementation + return new Observable((subscriber) => { + let skipped = 0; + + const subscription = this.subscribe({ + next: (value) => { + if (skipped < count) { + skipped++; + } else { + subscriber.next(value); + } + }, + error: (err) => subscriber.error(err), + complete: () => subscriber.complete(), + }); + + return () => subscription.unsubscribe(); + }); } /** @@ -107,15 +166,11 @@ class Observable { * @returns {Observable} Observable that emits array values */ static from(array) { - // TODO: Implement from - - // Return new Observable that: - // - Emits each array element - // - Completes after last element - return new Observable((subscriber) => { - // subscriber.next(...) for each - // subscriber.complete() + for (const value of array) { + subscriber.next(value); + } + subscriber.complete(); }); } @@ -125,10 +180,6 @@ class Observable { * @returns {Observable} Observable that emits single value */ static of(...values) { - // TODO: Implement of - - // Return new Observable that emits all values then completes - return Observable.from(values); } } diff --git a/10-lru-cache/index.js b/10-lru-cache/index.js index f087769..d4b9c70 100644 --- a/10-lru-cache/index.js +++ b/10-lru-cache/index.js @@ -9,11 +9,8 @@ class LRUCache { * @param {number} capacity - Maximum number of items */ constructor(capacity) { - // TODO: Initialize the cache - // Step 1: Store capacity - // this.capacity = capacity; - // Step 2: Create storage (Map recommended) - // this.cache = new Map(); + this._capacity = capacity; + this.cache = new Map(); } /** @@ -22,18 +19,16 @@ class LRUCache { * @returns {*} Value or undefined if not found */ get(key) { - // TODO: Implement get + if (!this.cache.has(key)) { + return undefined; + } - // Step 1: Check if key exists + // Move to end (most recent) + const value = this.cache.get(key); + this.cache.delete(key); + this.cache.set(key, value); - // Step 2: If exists: - // - Get the value - // - Move to end (most recent) - // - Return value - - // Step 3: If not exists, return undefined - - throw new Error("Not implemented"); + return value; } /** @@ -42,10 +37,17 @@ class LRUCache { * @param {*} value - Value to store */ put(key, value) { - // TODO: Implement put - // Step 1: If key already exists, delete it first (to update position) - // Step 2: If at capacity, evict least recently used (first item) - // Step 3: Add the new key-value pair (goes to end = most recent) + // If key exists, delete it first to update position + if (this.cache.has(key)) { + this.cache.delete(key); + } else if (this.cache.size >= this._capacity) { + // At capacity, evict least recently used (first item) + const firstKey = this.cache.keys().next().value; + this.cache.delete(firstKey); + } + + // Add new key-value pair (goes to end = most recent) + this.cache.set(key, value); } /** @@ -54,9 +56,7 @@ class LRUCache { * @returns {boolean} */ has(key) { - // TODO: Implement has - - throw new Error("Not implemented"); + return this.cache.has(key); } /** @@ -65,17 +65,14 @@ class LRUCache { * @returns {boolean} true if key existed */ delete(key) { - // TODO: Implement delete - - throw new Error("Not implemented"); + return this.cache.delete(key); } /** * Clear all items */ clear() { - // TODO: Implement clear - throw new Error("Not implemented"); + this.cache.clear(); } /** @@ -83,9 +80,7 @@ class LRUCache { * @returns {number} */ get size() { - // TODO: Return current size - - throw new Error("Not implemented"); + return this.cache.size; } /** @@ -93,9 +88,7 @@ class LRUCache { * @returns {Array} Array of keys */ keys() { - // TODO: Return array of keys - - throw new Error("Not implemented"); + return Array.from(this.cache.keys()); } /** @@ -103,9 +96,7 @@ class LRUCache { * @returns {Array} Array of values */ values() { - // TODO: Return array of values - - throw new Error("Not implemented"); + return Array.from(this.cache.values()); } } diff --git a/11-singleton/index.js b/11-singleton/index.js index 3c98305..49f8e66 100644 --- a/11-singleton/index.js +++ b/11-singleton/index.js @@ -8,31 +8,17 @@ * A class that only allows one instance to exist. */ class Singleton { - // TODO: Implement Singleton - - // Step 1: Create a static property to hold the instance - // static instance = null; - - // Step 2: Create a getInstance static method - // - Check if instance exists - // - If not, create it - // - Return the instance + static instance = null; static getInstance() { - // TODO: Implement getInstance - throw new Error("Not implemented"); + if (!Singleton.instance) { + Singleton.instance = new Singleton(); + } + return Singleton.instance; } - // Step 3: Optionally prevent direct instantiation - // constructor() { - // if (Singleton.instance) { - // throw new Error('Use Singleton.getInstance()'); - // } - // } - - // Step 4: Add a reset method for testing static resetInstance() { - // TODO: Reset the instance to null + Singleton.instance = null; } } @@ -45,26 +31,17 @@ class Singleton { * @returns {Object} Object with getInstance method */ function createSingleton(Class) { - // TODO: Implement createSingleton - - // Step 1: Create a closure variable to hold the instance - // let instance = null; - - // Step 2: Return an object with getInstance method - // getInstance should: - // - Accept arguments to pass to constructor - // - Only create instance on first call - // - Return the same instance on subsequent calls - - // Step 3: Optionally add resetInstance method + let instance = null; return { getInstance: (...args) => { - // TODO: Implement - throw new Error("Not implemented"); + if (!instance) { + instance = new Class(...args); + } + return instance; }, resetInstance: () => { - // TODO: Implement + instance = null; }, }; } diff --git a/12-factory-pattern/index.js b/12-factory-pattern/index.js index 3e0bd14..f4c88e8 100644 --- a/12-factory-pattern/index.js +++ b/12-factory-pattern/index.js @@ -61,8 +61,6 @@ class Triangle { * A factory object that creates shapes based on type. */ const ShapeFactory = { - // TODO: Implement create method - /** * Create a shape instance * @param {string} type - Shape type ('circle', 'rectangle', 'triangle') @@ -70,12 +68,16 @@ const ShapeFactory = { * @returns {Object} Shape instance */ create(type, options) { - // TODO: Implement factory logic - - // Use switch or object lookup to create the right shape - // Throw error for unknown types - - return null; // Replace with implementation + switch (type) { + case "circle": + return new Circle(options); + case "rectangle": + return new Rectangle(options); + case "triangle": + return new Triangle(options); + default: + throw new Error(`Unknown shape type: ${type}`); + } }, }; @@ -86,8 +88,7 @@ const ShapeFactory = { */ class Factory { constructor() { - // TODO: Initialize registry - // this.registry = new Map(); + this.registry = new Map(); } /** @@ -99,8 +100,7 @@ class Factory { * @param {Function} [options.validate] - Validation function */ register(type, Class, options = {}) { - // TODO: Implement register - // Store the class and options in the registry + this.registry.set(type, { Class, options }); } /** @@ -109,9 +109,7 @@ class Factory { * @returns {boolean} true if type was registered */ unregister(type) { - // TODO: Implement unregister - - throw new Error("Not implemented"); + return this.registry.delete(type); } /** @@ -121,19 +119,32 @@ class Factory { * @returns {Object} Instance of the type */ create(type, args = {}) { - // TODO: Implement create - - // Step 1: Check if type is registered - - // Step 2: Get the class and options + // Check if type is registered + if (!this.registry.has(type)) { + throw new Error(`Type '${type}' is not registered`); + } - // Step 3: Validate required fields (if specified) + // Get the class and options + const { Class, options } = this.registry.get(type); - // Step 4: Run custom validation (if specified) + // Validate required fields + if (options.required) { + for (const field of options.required) { + if (!(field in args)) { + throw new Error(`Missing required field: ${field}`); + } + } + } - // Step 5: Create and return instance + // Run custom validation + if (options.validate) { + if (!options.validate(args)) { + throw new Error(`Validation failed for type '${type}'`); + } + } - return null; // Replace with implementation + // Create and return instance + return new Class(args); } /** @@ -142,9 +153,7 @@ class Factory { * @returns {boolean} */ has(type) { - // TODO: Implement has - - throw new Error("Not implemented"); + return this.registry.has(type); } /** @@ -152,17 +161,14 @@ class Factory { * @returns {string[]} */ getTypes() { - // TODO: Implement getTypes - - throw new Error("Not implemented"); + return Array.from(this.registry.keys()); } /** * Clear all registered types */ clear() { - // TODO: Implement clear - throw new Error("Not implemented"); + this.registry.clear(); } } diff --git a/13-decorator-pattern/index.js b/13-decorator-pattern/index.js index 6a3646f..f8a5250 100644 --- a/13-decorator-pattern/index.js +++ b/13-decorator-pattern/index.js @@ -11,22 +11,12 @@ * @returns {Function} Decorated function */ function withLogging(fn) { - // TODO: Implement withLogging - - // Step 1: Return a new function that wraps fn - - // Step 2: Log the function name and arguments - - // Step 3: Call the original function - - // Step 4: Log the return value - - // Step 5: Return the result - - // Note: Preserve 'this' context using apply/call - - // Broken: throws error - throw new Error("Not implemented"); + return function (...args) { + log(`Calling ${fn.name} with args: ${JSON.stringify(args)}`); + const result = fn.apply(this, args); + log(`${fn.name} returned: ${JSON.stringify(result)}`); + return result; + }; } /** @@ -38,19 +28,13 @@ function withLogging(fn) { * @returns {Function} Decorated function */ function withTiming(fn) { - // TODO: Implement withTiming - - // Step 1: Return a new function - - // Step 2: Record start time (performance.now() or Date.now()) - - // Step 3: Call original function - - // Step 4: Calculate and log duration - - // Step 5: Return result - - return () => undefined; // Broken placeholder + return function (...args) { + const start = Date.now(); + const result = fn.apply(this, args); + const duration = Date.now() - start; + log(`${fn.name} took ${duration}ms`); + return result; + }; } /** @@ -63,20 +47,17 @@ function withTiming(fn) { * @returns {Function} Decorated function */ function withRetry(fn, maxRetries = 3) { - // TODO: Implement withRetry - - // Step 1: Return a new function - - // Step 2: Track attempt count - - // Step 3: Loop up to maxRetries: - // - Try to call fn - // - On success, return result - // - On failure, increment attempts and continue - - // Step 4: If all retries fail, throw the last error - - return () => undefined; // Broken placeholder + return function (...args) { + let lastError; + for (let attempt = 0; attempt <= maxRetries; attempt++) { + try { + return fn.apply(this, args); + } catch (error) { + lastError = error; + } + } + throw lastError; + }; } /** @@ -88,11 +69,17 @@ function withRetry(fn, maxRetries = 3) { * @returns {Function} Decorated function with cache */ function withMemoize(fn) { - // TODO: Implement withMemoize - - // Similar to memoization assignment but as a decorator - - return () => undefined; // Broken placeholder + const cache = new Map(); + + return function (...args) { + const key = JSON.stringify(args); + if (cache.has(key)) { + return cache.get(key); + } + const result = fn.apply(this, args); + cache.set(key, result); + return result; + }; } /** @@ -105,17 +92,12 @@ function withMemoize(fn) { * @returns {Function} Decorated function */ function withValidation(fn, validator) { - // TODO: Implement withValidation - - // Step 1: Return a new function - - // Step 2: Call validator with arguments - - // Step 3: If validation fails, throw error - - // Step 4: If passes, call original function - - return () => undefined; // Broken placeholder + return function (...args) { + if (!validator(...args)) { + throw new Error("Validation failed"); + } + return fn.apply(this, args); + }; } /** @@ -128,18 +110,19 @@ function withValidation(fn, validator) { * @returns {Object} Object with cached method */ function withCache(obj, methodName) { - // TODO: Implement withCache - - // Step 1: Get the original method - - // Step 2: Create a cache (Map) - - // Step 3: Replace the method with a caching wrapper - - // Step 4: Return the object + const original = obj[methodName]; + const cache = new Map(); + + obj[methodName] = function (...args) { + const key = JSON.stringify(args); + if (cache.has(key)) { + return cache.get(key); + } + const result = original.apply(this, args); + cache.set(key, result); + return result; + }; - // Broken: deletes the method instead of caching it - delete obj[methodName]; return obj; } @@ -153,14 +136,8 @@ function withCache(obj, methodName) { * @returns {Function} Composed decorator */ function compose(...decorators) { - // TODO: Implement compose - - // Return a function that takes fn and applies all decorators - - // Example: compose(a, b, c)(fn) = a(b(c(fn))) - return (fn) => { - throw new Error("Not implemented"); + return decorators.reduceRight((acc, decorator) => decorator(acc), fn); }; } @@ -173,12 +150,8 @@ function compose(...decorators) { * @returns {Function} Piped decorator */ function pipe(...decorators) { - // TODO: Implement pipe - - // Same as compose but left-to-right - return (fn) => { - throw new Error("Not implemented"); + return decorators.reduce((acc, decorator) => decorator(acc), fn); }; } diff --git a/14-middleware-pipeline/index.js b/14-middleware-pipeline/index.js index 8db7620..10f7645 100644 --- a/14-middleware-pipeline/index.js +++ b/14-middleware-pipeline/index.js @@ -5,8 +5,7 @@ */ class Pipeline { constructor() { - // TODO: Initialize middleware array - // this.middleware = []; + this.middleware = []; } /** @@ -15,15 +14,11 @@ class Pipeline { * @returns {Pipeline} this (for chaining) */ use(fn) { - // TODO: Implement use - - // Step 1: Validate fn is a function - - // Step 2: Add to middleware array - - // Step 3: Return this for chaining - - return null; // Broken: should return this + if (typeof fn !== "function") { + throw new TypeError("Middleware must be a function"); + } + this.middleware.push(fn); + return this; } /** @@ -32,21 +27,20 @@ class Pipeline { * @returns {Promise} Resolves when pipeline completes */ run(context) { - // TODO: Implement run - - // Step 1: Create a dispatch function that: - // - Takes an index - // - Gets middleware at that index - // - If no middleware, resolve - // - Otherwise, call middleware with context and next function - // - next = () => dispatch(index + 1) + const dispatch = (index) => { + if (index >= this.middleware.length) { + return Promise.resolve(); + } + + const fn = this.middleware[index]; + try { + return Promise.resolve(fn(context, () => dispatch(index + 1))); + } catch (err) { + return Promise.reject(err); + } + }; - // Step 2: Start dispatch at index 0 - - // Step 3: Return promise for async support - - // Broken: rejects instead of resolving - return Promise.reject(new Error("Not implemented")); + return dispatch(0); } /** @@ -54,10 +48,6 @@ class Pipeline { * @returns {Function} Composed middleware function */ compose() { - // TODO: Implement compose - - // Return a function that takes context and runs the pipeline - return (context) => this.run(context); } } @@ -71,27 +61,25 @@ class Pipeline { * @returns {Function} Composed function (context) => Promise */ function compose(middleware) { - // TODO: Implement compose - // Validate all items are functions - - // Return a function that: - // - Takes context - // - Creates dispatch(index) that calls middleware[index] - // - Returns dispatch(0) + for (const fn of middleware) { + if (typeof fn !== "function") { + throw new TypeError("Middleware must be composed of functions"); + } + } return function (context) { function dispatch(index) { - // TODO: Implement dispatch - - // Step 1: Get middleware at index - // Step 2: If none, return resolved promise - // Step 3: Create next function = () => dispatch(index + 1) - // Step 4: Call middleware with (context, next) - // Step 5: Return as promise - - // Broken: rejects instead of resolving - return Promise.reject(new Error("Not implemented")); + if (index >= middleware.length) { + return Promise.resolve(); + } + + const fn = middleware[index]; + try { + return Promise.resolve(fn(context, () => dispatch(index + 1))); + } catch (err) { + return Promise.reject(err); + } } return dispatch(0); @@ -106,15 +94,11 @@ function compose(middleware) { * @returns {Function} Conditional middleware */ function when(condition, middleware) { - // TODO: Implement when - - // Return middleware that: - // - Checks condition(ctx) - // - If true, runs middleware - // - If false, just calls next() - return (ctx, next) => { - throw new Error("Not implemented"); + if (condition(ctx)) { + return middleware(ctx, next); + } + return next(); }; } @@ -125,14 +109,12 @@ function when(condition, middleware) { * @returns {Function} Error handling middleware */ function errorMiddleware(errorHandler) { - // TODO: Implement errorMiddleware - - // Return middleware that: - // - Wraps next() in try/catch - // - Calls errorHandler if error thrown - return async (ctx, next) => { - throw new Error("Not implemented"); + try { + await next(); + } catch (err) { + errorHandler(err, ctx); + } }; } diff --git a/15-dependency-injection/index.js b/15-dependency-injection/index.js index e4995b5..4267f69 100644 --- a/15-dependency-injection/index.js +++ b/15-dependency-injection/index.js @@ -3,8 +3,7 @@ */ class Container { constructor() { - // TODO: Initialize registry - // this.registry = new Map(); + this.registry = new Map(); } /** @@ -16,9 +15,13 @@ class Container { * @param {boolean} [options.singleton=false] - Whether to create singleton */ register(name, Class, dependencies = [], options = {}) { - // TODO: Implement register - // Store in registry: - // { type: 'class', Class, dependencies, singleton, instance: null } + this.registry.set(name, { + type: "class", + Class, + dependencies, + singleton: options.singleton || false, + instance: null, + }); } /** @@ -27,9 +30,10 @@ class Container { * @param {*} instance - Instance to register */ registerInstance(name, instance) { - // TODO: Implement registerInstance - // Store in registry: - // { type: 'instance', instance } + this.registry.set(name, { + type: "instance", + instance, + }); } /** @@ -40,9 +44,13 @@ class Container { * @param {Object} [options={}] - Registration options */ registerFactory(name, factory, dependencies = [], options = {}) { - // TODO: Implement registerFactory - // Store in registry: - // { type: 'factory', factory, dependencies, singleton, instance: null } + this.registry.set(name, { + type: "factory", + factory, + dependencies, + singleton: options.singleton || false, + instance: null, + }); } /** @@ -52,32 +60,56 @@ class Container { * @returns {*} The resolved instance */ resolve(name, resolutionStack = new Set()) { - // TODO: Implement resolve - - // Step 1: Check if service is registered - // Throw error if not found - - // Step 2: Check for circular dependencies - // If name is already in resolutionStack, throw error - - // Step 3: Get registration from registry - - // Step 4: Handle different types: - - // For 'instance': - // - Return the stored instance - - // For 'class' or 'factory': - // - If singleton and instance exists, return instance - // - Add name to resolutionStack - // - Resolve all dependencies recursively - // - Create instance (new Class(...deps) or factory(...deps)) - // - Remove name from resolutionStack - // - If singleton, cache instance - // - Return instance - - // Broken: returns undefined (causes test assertions to fail) - return undefined; + // Check if service is registered + if (!this.registry.has(name)) { + throw new Error(`Service '${name}' is not registered`); + } + + // Check for circular dependencies + if (resolutionStack.has(name)) { + throw new Error(`Circular dependency detected: ${name}`); + } + + const registration = this.registry.get(name); + + // Handle instance type + if (registration.type === "instance") { + return registration.instance; + } + + // Handle class and factory types + if (registration.type === "class" || registration.type === "factory") { + // Return cached singleton if exists + if (registration.singleton && registration.instance !== null) { + return registration.instance; + } + + // Add to resolution stack + resolutionStack.add(name); + + // Resolve dependencies + const deps = registration.dependencies.map((dep) => + this.resolve(dep, resolutionStack), + ); + + // Create instance + let instance; + if (registration.type === "class") { + instance = new registration.Class(...deps); + } else { + instance = registration.factory(...deps); + } + + // Remove from resolution stack + resolutionStack.delete(name); + + // Cache if singleton + if (registration.singleton) { + registration.instance = instance; + } + + return instance; + } } /** @@ -86,8 +118,7 @@ class Container { * @returns {boolean} */ has(name) { - // TODO: Implement has - throw new Error("Not implemented"); + return this.registry.has(name); } /** @@ -96,16 +127,14 @@ class Container { * @returns {boolean} true if was registered */ unregister(name) { - // TODO: Implement unregister - throw new Error("Not implemented"); + return this.registry.delete(name); } /** * Clear all registrations */ clear() { - // TODO: Implement clear - throw new Error("Not implemented"); + this.registry.clear(); } /** @@ -113,8 +142,7 @@ class Container { * @returns {string[]} */ getRegistrations() { - // TODO: Implement getRegistrations - throw new Error("Not implemented"); + return Array.from(this.registry.keys()); } } @@ -125,14 +153,16 @@ class Container { * @returns {Container} Child container */ function createChildContainer(parent) { - // TODO: Implement createChildContainer + const child = new Container(); + const originalResolve = child.resolve.bind(child); - // Create a new container that: - // - First checks its own registry - // - Falls back to parent for unregistered services + child.resolve = function (name, resolutionStack = new Set()) { + if (this.registry.has(name)) { + return originalResolve(name, resolutionStack); + } + return parent.resolve(name, resolutionStack); + }; - const child = new Container(); - // Override resolve to check parent... return child; } diff --git a/16-state-machine/index.js b/16-state-machine/index.js index d0bad18..2c3fd91 100644 --- a/16-state-machine/index.js +++ b/16-state-machine/index.js @@ -10,13 +10,17 @@ class StateMachine { * @param {Object} [config.context] - Initial context data */ constructor(config) { - // TODO: Implement constructor - // Step 1: Validate config has initial and states - // Step 2: Store configuration - // this.config = config; - // this.currentState = config.initial; - // this.context = config.context || {}; - // Step 3: Validate initial state exists in states + if (!config.initial || !config.states) { + throw new Error("Config must have initial and states"); + } + + if (!config.states[config.initial]) { + throw new Error(`Initial state '${config.initial}' not found in states`); + } + + this.config = config; + this.currentState = config.initial; + this.context = config.context || {}; } /** @@ -24,8 +28,7 @@ class StateMachine { * @returns {string} */ get state() { - // TODO: Return current state - throw new Error("Not implemented"); + return this.currentState; } /** @@ -35,27 +38,39 @@ class StateMachine { * @returns {boolean} Whether transition was successful */ transition(event, payload) { - // TODO: Implement transition + const stateConfig = this.config.states[this.currentState]; - // Step 1: Get current state config + // Check if event is valid for current state + if (!stateConfig.on || !stateConfig.on[event]) { + return false; + } - // Step 2: Check if event is valid for current state - // Return false if not + const transitionConfig = stateConfig.on[event]; - // Step 3: Get transition config (can be string or object) - // If string: target = transition - // If object: { target, guard, action } + // Handle string or object transition + let target, guard, action; + if (typeof transitionConfig === "string") { + target = transitionConfig; + } else { + target = transitionConfig.target; + guard = transitionConfig.guard; + action = transitionConfig.action; + } - // Step 4: Check guard if present - // If guard returns false, return false - - // Step 5: Update state to target + // Check guard if present + if (guard && !guard(this.context, payload)) { + return false; + } - // Step 6: Call action if present + // Update state + this.currentState = target; - // Step 7: Return true + // Call action if present + if (action) { + action(this.context, payload); + } - throw new Error("Not implemented"); + return true; } /** @@ -64,12 +79,20 @@ class StateMachine { * @returns {boolean} */ can(event) { - // TODO: Implement can + const stateConfig = this.config.states[this.currentState]; + + if (!stateConfig.on || !stateConfig.on[event]) { + return false; + } + + const transitionConfig = stateConfig.on[event]; - // Check if event exists for current state // Check guard if present + if (typeof transitionConfig === "object" && transitionConfig.guard) { + return transitionConfig.guard(this.context); + } - throw new Error("Not implemented"); + return true; } /** @@ -77,11 +100,13 @@ class StateMachine { * @returns {string[]} Array of event names */ getAvailableTransitions() { - // TODO: Implement getAvailableTransitions + const stateConfig = this.config.states[this.currentState]; - // Return array of event names from current state's 'on' config + if (!stateConfig.on) { + return []; + } - throw new Error("Not implemented"); + return Object.keys(stateConfig.on); } /** @@ -89,8 +114,7 @@ class StateMachine { * @returns {Object} */ getContext() { - // TODO: Return context - throw new Error("Not implemented"); + return this.context; } /** @@ -98,9 +122,11 @@ class StateMachine { * @param {Object|Function} updater - New context or updater function */ updateContext(updater) { - // TODO: Implement updateContext - // If updater is function: this.context = updater(this.context) - // If updater is object: merge with existing context + if (typeof updater === "function") { + this.context = updater(this.context); + } else { + this.context = { ...this.context, ...updater }; + } } /** @@ -108,8 +134,8 @@ class StateMachine { * @returns {boolean} */ isFinal() { - // TODO: Check if current state has no transitions - throw new Error("Not implemented"); + const stateConfig = this.config.states[this.currentState]; + return !stateConfig.on || Object.keys(stateConfig.on).length === 0; } /** @@ -117,8 +143,10 @@ class StateMachine { * @param {Object} [newContext] - Optional new context */ reset(newContext) { - // TODO: Reset to initial state - // Optionally reset context + this.currentState = this.config.initial; + if (newContext !== undefined) { + this.context = newContext; + } } } @@ -129,11 +157,6 @@ class StateMachine { * @returns {Function} Factory function that creates machines */ function createMachine(config) { - // TODO: Implement createMachine - - // Return a function that creates new StateMachine instances - // with the given config - return () => new StateMachine(config); } diff --git a/17-command-pattern/index.js b/17-command-pattern/index.js index e8cd005..a918d94 100644 --- a/17-command-pattern/index.js +++ b/17-command-pattern/index.js @@ -9,9 +9,8 @@ */ class CommandManager { constructor() { - // TODO: Initialize stacks - // this.undoStack = []; - // this.redoStack = []; + this.undoStack = []; + this.redoStack = []; } /** @@ -19,10 +18,9 @@ class CommandManager { * @param {Object} command - Command with execute() method */ execute(command) { - // TODO: Implement execute - // Step 1: Call command.execute() - // Step 2: Push to undo stack - // Step 3: Clear redo stack (new action invalidates redo history) + command.execute(); + this.undoStack.push(command); + this.redoStack = []; } /** @@ -30,19 +28,14 @@ class CommandManager { * @returns {boolean} Whether undo was performed */ undo() { - // TODO: Implement undo + if (this.undoStack.length === 0) { + return false; + } - // Step 1: Check if undo stack is empty - - // Step 2: Pop command from undo stack - - // Step 3: Call command.undo() - - // Step 4: Push to redo stack - - // Step 5: Return true - - throw new Error("Not implemented"); + const command = this.undoStack.pop(); + command.undo(); + this.redoStack.push(command); + return true; } /** @@ -50,19 +43,14 @@ class CommandManager { * @returns {boolean} Whether redo was performed */ redo() { - // TODO: Implement redo - - // Step 1: Check if redo stack is empty - - // Step 2: Pop command from redo stack - - // Step 3: Call command.execute() - - // Step 4: Push to undo stack - - // Step 5: Return true + if (this.redoStack.length === 0) { + return false; + } - throw new Error("Not implemented"); + const command = this.redoStack.pop(); + command.execute(); + this.undoStack.push(command); + return true; } /** @@ -70,8 +58,7 @@ class CommandManager { * @returns {boolean} */ canUndo() { - // TODO: Return whether undo stack has items - throw new Error("Not implemented"); + return this.undoStack.length > 0; } /** @@ -79,8 +66,7 @@ class CommandManager { * @returns {boolean} */ canRedo() { - // TODO: Return whether redo stack has items - throw new Error("Not implemented"); + return this.redoStack.length > 0; } /** @@ -88,15 +74,15 @@ class CommandManager { * @returns {Object[]} */ get history() { - // TODO: Return copy of undo stack - throw new Error("Not implemented"); + return [...this.undoStack]; } /** * Clear all history */ clear() { - // TODO: Clear both stacks + this.undoStack = []; + this.redoStack = []; } } @@ -105,18 +91,17 @@ class CommandManager { */ class AddCommand { constructor(calculator, value) { - // TODO: Store calculator and value - // this.calculator = calculator; - // this.value = value; + this.calculator = calculator; + this.value = value; this.description = `Add ${value}`; } execute() { - // TODO: Add value to calculator.value + this.calculator.value += this.value; } undo() { - // TODO: Subtract value from calculator.value + this.calculator.value -= this.value; } } @@ -125,16 +110,17 @@ class AddCommand { */ class SubtractCommand { constructor(calculator, value) { - // TODO: Store calculator and value + this.calculator = calculator; + this.value = value; this.description = `Subtract ${value}`; } execute() { - // TODO: Subtract value from calculator.value + this.calculator.value -= this.value; } undo() { - // TODO: Add value to calculator.value + this.calculator.value += this.value; } } @@ -143,17 +129,19 @@ class SubtractCommand { */ class MultiplyCommand { constructor(calculator, value) { - // TODO: Store calculator, value, and previous value for undo + this.calculator = calculator; + this.value = value; + this.previousValue = null; this.description = `Multiply by ${value}`; } execute() { - // TODO: Multiply calculator.value by value - // Save previous value for undo + this.previousValue = this.calculator.value; + this.calculator.value *= this.value; } undo() { - // TODO: Restore previous value + this.calculator.value = this.previousValue; } } @@ -162,17 +150,19 @@ class MultiplyCommand { */ class DivideCommand { constructor(calculator, value) { - // TODO: Store calculator, value, and previous value for undo + this.calculator = calculator; + this.value = value; + this.previousValue = null; this.description = `Divide by ${value}`; } execute() { - // TODO: Divide calculator.value by value - // Save previous value for undo + this.previousValue = this.calculator.value; + this.calculator.value /= this.value; } undo() { - // TODO: Restore previous value + this.calculator.value = this.previousValue; } } @@ -183,8 +173,7 @@ class DivideCommand { */ class MacroCommand { constructor(commands = []) { - // TODO: Store commands array - // this.commands = commands; + this.commands = commands; this.description = "Macro"; } @@ -193,15 +182,19 @@ class MacroCommand { * @param {Object} command */ add(command) { - // TODO: Add command to array + this.commands.push(command); } execute() { - // TODO: Execute all commands in order + for (const command of this.commands) { + command.execute(); + } } undo() { - // TODO: Undo all commands in reverse order + for (let i = this.commands.length - 1; i >= 0; i--) { + this.commands[i].undo(); + } } } @@ -212,16 +205,19 @@ class MacroCommand { */ class SetValueCommand { constructor(calculator, value) { - // TODO: Store calculator, new value, and previous value + this.calculator = calculator; + this.value = value; + this.previousValue = null; this.description = `Set to ${value}`; } execute() { - // TODO: Save previous, set new value + this.previousValue = this.calculator.value; + this.calculator.value = this.value; } undo() { - // TODO: Restore previous value + this.calculator.value = this.previousValue; } } diff --git a/18-strategy-pattern/index.js b/18-strategy-pattern/index.js index f5eee2c..ca8ba32 100644 --- a/18-strategy-pattern/index.js +++ b/18-strategy-pattern/index.js @@ -13,18 +13,15 @@ */ class SortContext { constructor(strategy) { - // TODO: Store strategy - // this.strategy = strategy; + this.strategy = strategy; } setStrategy(strategy) { - // TODO: Update strategy + this.strategy = strategy; } sort(array) { - // TODO: Delegate to strategy - // Return sorted copy, don't mutate original - throw new Error("Not implemented"); + return this.strategy.sort(array); } } @@ -33,10 +30,18 @@ class SortContext { */ class BubbleSort { sort(array) { - // TODO: Implement bubble sort - // Return new sorted array - - return ["NOT_IMPLEMENTED"]; // Broken: Replace with implementation + const arr = [...array]; + const n = arr.length; + + for (let i = 0; i < n - 1; i++) { + for (let j = 0; j < n - i - 1; j++) { + if (arr[j] > arr[j + 1]) { + [arr[j], arr[j + 1]] = [arr[j + 1], arr[j]]; + } + } + } + + return arr; } } @@ -45,10 +50,15 @@ class BubbleSort { */ class QuickSort { sort(array) { - // TODO: Implement quick sort - // Return new sorted array - - return []; // Broken: Replace with implementation + if (array.length <= 1) return [...array]; + + const arr = [...array]; + const pivot = arr[Math.floor(arr.length / 2)]; + const left = arr.filter(x => x < pivot); + const middle = arr.filter(x => x === pivot); + const right = arr.filter(x => x > pivot); + + return [...this.sort(left), ...middle, ...this.sort(right)]; } } @@ -57,10 +67,28 @@ class QuickSort { */ class MergeSort { sort(array) { - // TODO: Implement merge sort - // Return new sorted array - - return []; // Broken: Replace with implementation + if (array.length <= 1) return [...array]; + + const mid = Math.floor(array.length / 2); + const left = this.sort(array.slice(0, mid)); + const right = this.sort(array.slice(mid)); + + return this.merge(left, right); + } + + merge(left, right) { + const result = []; + let i = 0, j = 0; + + while (i < left.length && j < right.length) { + if (left[i] <= right[j]) { + result.push(left[i++]); + } else { + result.push(right[j++]); + } + } + + return result.concat(left.slice(i)).concat(right.slice(j)); } } @@ -75,16 +103,15 @@ class MergeSort { */ class PricingContext { constructor(strategy) { - // TODO: Store strategy + this.strategy = strategy; } setStrategy(strategy) { - // TODO: Update strategy + this.strategy = strategy; } calculateTotal(items) { - // TODO: Delegate to strategy - throw new Error("Not implemented"); + return this.strategy.calculate(items); } } @@ -93,8 +120,7 @@ class PricingContext { */ class RegularPricing { calculate(items) { - // TODO: Sum all item prices - throw new Error("Not implemented"); + return items.reduce((sum, item) => sum + item.price, 0); } } @@ -103,14 +129,12 @@ class RegularPricing { */ class PercentageDiscount { constructor(percentage) { - // TODO: Store percentage (0-100) - // this.percentage = percentage; + this.percentage = percentage; } calculate(items) { - // TODO: Apply percentage discount - // total * (1 - percentage/100) - throw new Error("Not implemented"); + const total = items.reduce((sum, item) => sum + item.price, 0); + return total * (1 - this.percentage / 100); } } @@ -119,14 +143,12 @@ class PercentageDiscount { */ class FixedDiscount { constructor(amount) { - // TODO: Store fixed discount amount - // this.amount = amount; + this.amount = amount; } calculate(items) { - // TODO: Subtract fixed amount from total - // Don't go below 0 - throw new Error("Not implemented"); + const total = items.reduce((sum, item) => sum + item.price, 0); + return Math.max(0, total - this.amount); } } @@ -135,9 +157,16 @@ class FixedDiscount { */ class BuyOneGetOneFree { calculate(items) { - // TODO: Every second item is free - // Sort by price desc, charge only every other item - throw new Error("Not implemented"); + const sorted = [...items].sort((a, b) => b.price - a.price); + let total = 0; + + for (let i = 0; i < sorted.length; i++) { + if (i % 2 === 0) { + total += sorted[i].price; + } + } + + return total; } } @@ -148,14 +177,19 @@ class BuyOneGetOneFree { */ class TieredDiscount { constructor(tiers) { - // TODO: Store tiers - // tiers = [{ threshold: 100, discount: 10 }, { threshold: 200, discount: 20 }] - // this.tiers = tiers; + this.tiers = tiers.sort((a, b) => b.threshold - a.threshold); } calculate(items) { - // TODO: Apply tier discount based on subtotal - throw new Error("Not implemented"); + const total = items.reduce((sum, item) => sum + item.price, 0); + + for (const tier of this.tiers) { + if (total >= tier.threshold) { + return total * (1 - tier.discount / 100); + } + } + + return total; } } @@ -168,16 +202,15 @@ class TieredDiscount { */ class ValidationContext { constructor(strategy) { - // TODO: Store strategy + this.strategy = strategy; } setStrategy(strategy) { - // TODO: Update strategy + this.strategy = strategy; } validate(data) { - // TODO: Delegate to strategy - throw new Error("Not implemented"); + return this.strategy.validate(data); } } @@ -186,9 +219,24 @@ class ValidationContext { */ class StrictValidation { validate(data) { - // TODO: Strict rules - all fields required, strict format - // Return { valid: boolean, errors: string[] } - throw new Error("Not implemented"); + const errors = []; + + if (!data.name || data.name.trim() === '') { + errors.push('Name is required'); + } + + if (!data.email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) { + errors.push('Valid email is required'); + } + + if (!data.age || typeof data.age !== 'number' || data.age < 0) { + errors.push('Valid age is required'); + } + + return { + valid: errors.length === 0, + errors + }; } } @@ -197,8 +245,16 @@ class StrictValidation { */ class LenientValidation { validate(data) { - // TODO: Lenient rules - only critical fields required - return { valid: false, errors: ["Not implemented"] }; // Broken: Replace with implementation + const errors = []; + + if (data.name !== undefined && data.name.trim() === '') { + errors.push('Name cannot be empty if provided'); + } + + return { + valid: errors.length === 0, + errors + }; } } @@ -213,22 +269,19 @@ class LenientValidation { */ class StrategyRegistry { constructor() { - // TODO: Initialize registry map - // this.strategies = new Map(); + this.strategies = new Map(); } register(name, strategy) { - // TODO: Store strategy by name + this.strategies.set(name, strategy); } get(name) { - // TODO: Return strategy by name - throw new Error("Not implemented"); + return this.strategies.get(name) || null; } has(name) { - // TODO: Check if strategy exists - throw new Error("Not implemented"); + return this.strategies.has(name); } } diff --git a/19-proxy-pattern/index.js b/19-proxy-pattern/index.js index 6d2bf2b..d92c479 100644 --- a/19-proxy-pattern/index.js +++ b/19-proxy-pattern/index.js @@ -10,29 +10,19 @@ * @returns {Proxy} Proxy that validates on set */ function createValidatingProxy(target, validators) { - // TODO: Implement validating proxy - - // Create a Proxy with a handler that: - // - On 'set': check if validator exists for property - // - If validator returns false, throw Error - // - Otherwise, set the property - // - On 'get': return property value normally - return new Proxy(target, { set(obj, prop, value) { - // TODO: Implement set trap - // Check validators[prop](value) if validator exists - // Throw if validation fails - // Set property if passes - - // Broken: doesn't set at all (fails all tests) + if (validators[prop]) { + if (!validators[prop](value)) { + throw new Error(`Validation failed for property ${prop}`); + } + } + obj[prop] = value; return true; }, get(obj, prop) { - // TODO: Implement get trap - // Broken: returns wrong value - return "NOT_IMPLEMENTED"; + return obj[prop]; }, }); } @@ -45,27 +35,29 @@ function createValidatingProxy(target, validators) { * @returns {Proxy} Proxy that logs all operations */ function createLoggingProxy(target, logger) { - // TODO: Implement logging proxy - return new Proxy(target, { get(obj, prop) { - // TODO: Log 'get' and return value - throw new Error("Not implemented"); + const value = obj[prop]; + logger("get", prop, value); + return value; }, set(obj, prop, value) { - // TODO: Log 'set' and set value - throw new Error("Not implemented"); + logger("set", prop, value); + obj[prop] = value; + return true; }, deleteProperty(obj, prop) { - // TODO: Log 'delete' and delete property - throw new Error("Not implemented"); + logger("delete", prop, undefined); + delete obj[prop]; + return true; }, has(obj, prop) { - // TODO: Log 'has' and return result - throw new Error("Not implemented"); + const result = prop in obj; + logger("has", prop, result); + return result; }, }); } @@ -78,24 +70,27 @@ function createLoggingProxy(target, logger) { * @returns {Proxy} Proxy that caches method results */ function createCachingProxy(target, methodNames) { - // TODO: Implement caching proxy - - // Create cache storage - // const cache = new Map(); + const cache = new Map(); return new Proxy(target, { get(obj, prop) { - // TODO: Implement get trap - - // If prop is in methodNames and is a function: - // Return a wrapped function that: - // - Creates cache key from arguments - // - Returns cached result if exists - // - Otherwise, calls original, caches, and returns - - // Otherwise, return property normally - - throw new Error("Not implemented"); + const value = obj[prop]; + + if (methodNames.includes(prop) && typeof value === "function") { + return function (...args) { + const cacheKey = `${prop}:${JSON.stringify(args)}`; + + if (cache.has(cacheKey)) { + return cache.get(cacheKey); + } + + const result = value.apply(obj, args); + cache.set(cacheKey, result); + return result; + }; + } + + return value; }, }); } @@ -110,28 +105,29 @@ function createCachingProxy(target, methodNames) { * @returns {Proxy} Proxy that enforces access control */ function createAccessProxy(target, permissions) { - // TODO: Implement access control proxy - const { readable = [], writable = [] } = permissions; return new Proxy(target, { get(obj, prop) { - // TODO: Check if prop is in readable - // Throw if not allowed - // Broken: returns wrong value - return "NOT_IMPLEMENTED"; + if (!readable.includes(prop)) { + throw new Error(`Access denied: cannot read property ${prop}`); + } + return obj[prop]; }, set(obj, prop, value) { - // TODO: Check if prop is in writable - // Throw if not allowed - // Broken: doesn't actually set + if (!writable.includes(prop)) { + throw new Error(`Access denied: cannot write property ${prop}`); + } + obj[prop] = value; return true; }, deleteProperty(obj, prop) { - // TODO: Only allow if in writable - // Broken: doesn't delete + if (!writable.includes(prop)) { + throw new Error(`Access denied: cannot delete property ${prop}`); + } + delete obj[prop]; return true; }, }); @@ -144,8 +140,6 @@ function createAccessProxy(target, permissions) { * @returns {Proxy} Proxy that loads object on first access */ function createLazyProxy(loader) { - // TODO: Implement lazy loading proxy - let instance = null; let loaded = false; @@ -153,15 +147,20 @@ function createLazyProxy(loader) { {}, { get(obj, prop) { - // TODO: Load instance on first access - // if (!loaded) { instance = loader(); loaded = true; } - // return instance[prop] - throw new Error("Not implemented"); + if (!loaded) { + instance = loader(); + loaded = true; + } + return instance[prop]; }, set(obj, prop, value) { - // TODO: Load instance if needed, then set - throw new Error("Not implemented"); + if (!loaded) { + instance = loader(); + loaded = true; + } + instance[prop] = value; + return true; }, }, ); @@ -175,17 +174,19 @@ function createLazyProxy(loader) { * @returns {Proxy} Proxy that notifies on changes */ function createObservableProxy(target, onChange) { - // TODO: Implement observable proxy - return new Proxy(target, { set(obj, prop, value) { - // TODO: Call onChange(prop, value, oldValue) on change - throw new Error("Not implemented"); + const oldValue = obj[prop]; + obj[prop] = value; + onChange(prop, value, oldValue); + return true; }, deleteProperty(obj, prop) { - // TODO: Call onChange on delete - throw new Error("Not implemented"); + const oldValue = obj[prop]; + delete obj[prop]; + onChange(prop, undefined, oldValue); + return true; }, }); } diff --git a/20-builder-pattern/index.js b/20-builder-pattern/index.js index 69a1e1a..1379cc9 100644 --- a/20-builder-pattern/index.js +++ b/20-builder-pattern/index.js @@ -9,12 +9,11 @@ */ class QueryBuilder { constructor() { - // TODO: Initialize state - // this.selectCols = []; - // this.fromTable = null; - // this.whereClauses = []; - // this.orderByClauses = []; - // this.limitCount = null; + this.selectCols = []; + this.fromTable = null; + this.whereClauses = []; + this.orderByClauses = []; + this.limitCount = null; } /** @@ -23,8 +22,8 @@ class QueryBuilder { * @returns {QueryBuilder} this */ select(...columns) { - // TODO: Store columns - throw new Error("Not implemented"); + this.selectCols = columns; + return this; } /** @@ -33,8 +32,8 @@ class QueryBuilder { * @returns {QueryBuilder} this */ from(table) { - // TODO: Store table name - throw new Error("Not implemented"); + this.fromTable = table; + return this; } /** @@ -45,8 +44,8 @@ class QueryBuilder { * @returns {QueryBuilder} this */ where(column, operator, value) { - // TODO: Store where clause - throw new Error("Not implemented"); + this.whereClauses.push({ column, operator, value }); + return this; } /** @@ -56,8 +55,8 @@ class QueryBuilder { * @returns {QueryBuilder} this */ orderBy(column, direction = "ASC") { - // TODO: Store order by clause - throw new Error("Not implemented"); + this.orderByClauses.push({ column, direction }); + return this; } /** @@ -66,8 +65,8 @@ class QueryBuilder { * @returns {QueryBuilder} this */ limit(count) { - // TODO: Store limit - throw new Error("Not implemented"); + this.limitCount = count; + return this; } /** @@ -75,9 +74,27 @@ class QueryBuilder { * @returns {string} SQL query string */ build() { - // TODO: Build and return query string - // Format: SELECT cols FROM table WHERE clauses ORDER BY clause LIMIT n - throw new Error("Not implemented"); + let query = `SELECT ${this.selectCols.join(", ")} FROM ${this.fromTable}`; + + if (this.whereClauses.length > 0) { + const whereStr = this.whereClauses + .map(({ column, operator, value }) => `${column} ${operator} ${value}`) + .join(" AND "); + query += ` WHERE ${whereStr}`; + } + + if (this.orderByClauses.length > 0) { + const orderStr = this.orderByClauses + .map(({ column, direction }) => `${column} ${direction}`) + .join(", "); + query += ` ORDER BY ${orderStr}`; + } + + if (this.limitCount !== null) { + query += ` LIMIT ${this.limitCount}`; + } + + return query; } /** @@ -85,8 +102,12 @@ class QueryBuilder { * @returns {QueryBuilder} this */ reset() { - // TODO: Reset all state - throw new Error("Not implemented"); + this.selectCols = []; + this.fromTable = null; + this.whereClauses = []; + this.orderByClauses = []; + this.limitCount = null; + return this; } } @@ -97,13 +118,12 @@ class QueryBuilder { */ class HTMLBuilder { constructor() { - // TODO: Initialize state - // this.tagName = 'div'; - // this.idAttr = null; - // this.classes = []; - // this.attributes = {}; - // this.innerContent = ''; - // this.children = []; + this.tagName = "div"; + this.idAttr = null; + this.classes = []; + this.attributes = {}; + this.innerContent = ""; + this.children = []; } /** @@ -112,8 +132,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ tag(name) { - // TODO: Store tag name - throw new Error("Not implemented"); + this.tagName = name; + return this; } /** @@ -122,8 +142,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ id(id) { - // TODO: Store id - throw new Error("Not implemented"); + this.idAttr = id; + return this; } /** @@ -132,8 +152,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ class(...classNames) { - // TODO: Store classes - throw new Error("Not implemented"); + this.classes.push(...classNames); + return this; } /** @@ -143,8 +163,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ attr(name, value) { - // TODO: Store attribute - throw new Error("Not implemented"); + this.attributes[name] = value; + return this; } /** @@ -153,8 +173,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ content(content) { - // TODO: Store content - throw new Error("Not implemented"); + this.innerContent = content; + return this; } /** @@ -163,8 +183,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ child(childHtml) { - // TODO: Store child - throw new Error("Not implemented"); + this.children.push(childHtml); + return this; } /** @@ -172,9 +192,26 @@ class HTMLBuilder { * @returns {string} HTML element string */ build() { - // TODO: Build and return HTML string - // Format: content - throw new Error("Not implemented"); + let html = `<${this.tagName}`; + + if (this.idAttr) { + html += ` id="${this.idAttr}"`; + } + + if (this.classes.length > 0) { + html += ` class="${this.classes.join(" ")}"`; + } + + for (const [name, value] of Object.entries(this.attributes)) { + html += ` ${name}="${value}"`; + } + + html += ">"; + html += this.innerContent; + html += this.children.join(""); + html += ``; + + return html; } /** @@ -182,8 +219,13 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ reset() { - // TODO: Reset all state - throw new Error("Not implemented"); + this.tagName = "div"; + this.idAttr = null; + this.classes = []; + this.attributes = {}; + this.innerContent = ""; + this.children = []; + return this; } } @@ -194,13 +236,12 @@ class HTMLBuilder { */ class ConfigBuilder { constructor() { - // TODO: Initialize state - // this.config = { - // environment: 'development', - // database: null, - // features: [], - // logLevel: 'info' - // }; + this.config = { + environment: "development", + database: null, + features: [], + logLevel: "info", + }; } /** @@ -209,8 +250,8 @@ class ConfigBuilder { * @returns {ConfigBuilder} this */ setEnvironment(env) { - // TODO: Set environment - throw new Error("Not implemented"); + this.config.environment = env; + return this; } /** @@ -219,8 +260,8 @@ class ConfigBuilder { * @returns {ConfigBuilder} this */ setDatabase(dbConfig) { - // TODO: Set database config - throw new Error("Not implemented"); + this.config.database = dbConfig; + return this; } /** @@ -229,8 +270,10 @@ class ConfigBuilder { * @returns {ConfigBuilder} this */ enableFeature(feature) { - // TODO: Add feature to list - throw new Error("Not implemented"); + if (!this.config.features.includes(feature)) { + this.config.features.push(feature); + } + return this; } /** @@ -239,8 +282,8 @@ class ConfigBuilder { * @returns {ConfigBuilder} this */ disableFeature(feature) { - // TODO: Remove feature from list - throw new Error("Not implemented"); + this.config.features = this.config.features.filter((f) => f !== feature); + return this; } /** @@ -249,8 +292,8 @@ class ConfigBuilder { * @returns {ConfigBuilder} this */ setLogLevel(level) { - // TODO: Set log level - throw new Error("Not implemented"); + this.config.logLevel = level; + return this; } /** @@ -258,8 +301,12 @@ class ConfigBuilder { * @returns {Object} Configuration object */ build() { - // TODO: Return copy of config - throw new Error("Not implemented"); + return { + environment: this.config.environment, + database: this.config.database, + features: [...this.config.features], + logLevel: this.config.logLevel, + }; } } @@ -270,7 +317,12 @@ class ConfigBuilder { */ class RequestBuilder { constructor(baseUrl = "") { - // TODO: Initialize state + this.baseUrl = baseUrl; + this.httpMethod = "GET"; + this.urlPath = ""; + this.queryParams = {}; + this.headers = {}; + this.requestBody = null; } /** @@ -279,7 +331,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ method(method) { - throw new Error("Not implemented"); + this.httpMethod = method; + return this; } /** @@ -288,7 +341,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ path(path) { - throw new Error("Not implemented"); + this.urlPath = path; + return this; } /** @@ -298,7 +352,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ query(key, value) { - throw new Error("Not implemented"); + this.queryParams[key] = value; + return this; } /** @@ -308,7 +363,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ header(key, value) { - throw new Error("Not implemented"); + this.headers[key] = value; + return this; } /** @@ -317,7 +373,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ body(body) { - throw new Error("Not implemented"); + this.requestBody = body; + return this; } /** @@ -325,8 +382,22 @@ class RequestBuilder { * @returns {Object} Request config for fetch */ build() { - // TODO: Return fetch-compatible config - throw new Error("Not implemented"); + let url = this.baseUrl + this.urlPath; + + const queryString = Object.entries(this.queryParams) + .map(([key, value]) => `${key}=${value}`) + .join("&"); + + if (queryString) { + url += `?${queryString}`; + } + + return { + method: this.httpMethod, + url, + headers: { ...this.headers }, + body: this.requestBody, + }; } }