diff --git a/01-deep-clone/index.js b/01-deep-clone/index.js index e0281df..f5e6941 100644 --- a/01-deep-clone/index.js +++ b/01-deep-clone/index.js @@ -11,31 +11,68 @@ 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 + const primitives = ['number', 'string', 'boolean', 'symbol', 'bigint']; - // Step 2: Check for circular references using the visited WeakMap - // If we've seen this object before, return the cached clone + if (value == null || primitives.includes(typeof value)) return value; - // Step 3: Handle Date objects - // Create a new Date with the same time value + else if (visited.has(value)) return visited.get(value); - // Step 4: Handle RegExp objects - // Create a new RegExp with the same source and flags + else if (value instanceof Date) { + const clone = new Date(value.getTime()); + visited.set(value, clone); + return clone; + } - // Step 5: Handle Map objects - // Create a new Map and deep clone each key-value pair + else if (value instanceof RegExp){ + const clone = new RegExp(value.source, value.flags); + visited.set(value, clone); + return clone; + } - // Step 6: Handle Set objects - // Create a new Set and deep clone each value + else if (value instanceof Map){ + const clone = new Map(); + visited.set(value, clone); + value.forEach((val, key) => { + clone.set(deepClone(key, visited), deepClone(val, visited)); + }); + return clone; + } - // Step 7: Handle Arrays - // Create a new array and deep clone each element + else if (value instanceof Set){ + const clone = new Set(); + visited.set(value, clone); + value.forEach((val) => clone.add(deepClone(val, visited))); + return clone; + } - // Step 8: Handle plain Objects - // Create a new object and deep clone each property + else if (Array.isArray(value)){ + const clone = []; + visited.set(value, clone); + value.forEach((val) => clone.push(deepClone(val, visited))); + return clone; + } - return undefined; // Broken: Replace with your implementation + const clone = Object.create(Object.getPrototypeOf(value)); + + visited.set(value, clone); + + const properties = [ + ...Object.getOwnPropertyNames(value), + ...Object.getOwnPropertySymbols(value) + ] + + for (const property of properties){ + const descriptor = Object.getOwnPropertyDescriptor(value, property); + + if('value' in descriptor){ + descriptor.value = deepClone(descriptor.value, visited); + } + + Object.defineProperty(clone, property, descriptor); + + } + + return clone; } module.exports = { deepClone }; diff --git a/02-debounce-throttle/index.js b/02-debounce-throttle/index.js index 397281b..61208e1 100644 --- a/02-debounce-throttle/index.js +++ b/02-debounce-throttle/index.js @@ -11,19 +11,21 @@ function debounce(fn, delay) { // TODO: Implement debounce - // Step 1: Create a variable to store the timeout ID + let timeoutId = null; - // 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 + function debounced(...args) { + clearTimeout(timeoutId); + timeoutId = setTimeout(() => { + fn.apply(this, args); + }, delay); + } - // Step 3: Add a cancel() method to clear pending timeout + debounced.cancel = function(){ + clearTimeout(timeoutId); + timeoutId = null; + } - // Step 4: Return the debounced function - - // Return a placeholder that doesn't work - throw new Error("Not implemented"); + return debounced; } /** @@ -37,23 +39,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 + let inTrottle = false; + let timeoutId = null; + + function trottled(...args){ + if (!inTrottle){ + fn.apply(this, args); + inTrottle = true; + + setTimeout(()=> inTrottle = false, limit); + } + } - // Step 4: Return the throttled function + trottled.cancel = function(){ + clearTimeout(timeoutId); + inTrottle = false; + timeoutId = null; + } - // Return a placeholder that doesn't work - throw new Error("Not implemented"); + return trottled; } module.exports = { debounce, throttle }; diff --git a/03-custom-bind/index.js b/03-custom-bind/index.js index 3e691f9..4c02faa 100644 --- a/03-custom-bind/index.js +++ b/03-custom-bind/index.js @@ -11,29 +11,26 @@ * @returns {Function} A new bound function */ function customBind(fn, context, ...boundArgs) { - // TODO: Implement custom bind - // Step 1: Validate that fn is a function - // Throw TypeError if not + if (typeof fn !== 'function') throw new TypeError("fn is not a function"); - // 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 + const boundFunction = function(...args){ + + const fullArgs = [...boundArgs, ...args]; + + const isCountructCall = this instanceof boundFunction; - // 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 + const contextToCall = isCountructCall ? this : context; - // Step 4: Preserve the prototype for constructor usage - // boundFunction.prototype = Object.create(fn.prototype) + return fn.apply(contextToCall, fullArgs); + } - // Step 5: Return the bound function + if (fn.prototype){ + boundFunction.prototype = Object.create(fn.prototype); + } - // Return placeholder that doesn't work - throw new Error("Not implemented"); + + return boundFunction; } /** @@ -43,9 +40,24 @@ function customBind(fn, context, ...boundArgs) { * myFunction.customBind(context, ...args) */ -// Uncomment and implement: -// Function.prototype.customBind = function(context, ...boundArgs) { -// // Your implementation -// }; +Function.prototype.customBind = function(context, ...boundArgs) { + const fn = this; + + const boundFunction = function(...args){ + const fullArgs = [...boundArgs, ...args]; + + isCountructCall = this instanceof boundFunction; + + const contextToCall = isCountructCall ? this : context; + + return fn.apply(contextToCall, fullArgs) + } + + if (fn.prototype){ + boundFunction.prototype = Object.create(fn.prototype); + } + + return boundFunction; +}; module.exports = { customBind }; diff --git a/04-memoization/index.js b/04-memoization/index.js index dc0f09b..c81774f 100644 --- a/04-memoization/index.js +++ b/04-memoization/index.js @@ -11,46 +11,53 @@ * @returns {Function} Memoized function with cache control methods */ function memoize(fn, options = {}) { - // TODO: Implement memoization + + const { maxSize = Infinity, ttl = Infinity, keyGenerator = null } = options; - // Step 1: Extract options with defaults - // const { maxSize, ttl, keyGenerator } = options; + const cache = new Map(); - // Step 2: Create the cache (use Map for ordered keys) - // const cache = new Map(); + const generator = keyGenerator || ((...args) => JSON.stringify(args)); - // Step 3: Create default key generator - // Default: JSON.stringify(args) or args.join(',') + const memoized = function(...args){ + const cacheKey = generator(args); - // 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) + if (cache.has(cacheKey)){ - // 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; } - // }; + const entry = cache.get(cacheKey); - // Step 6: Return memoized function + let isExpired = ttl !== Infinity && (Date.now() - entry.timestamp) > ttl; + + if (!isExpired){ + return entry.value; + } + + cache.delete(cacheKey); + } + + const result = fn.apply(this, args); + + + cache.set(cacheKey, { + value: result, + timestamp: Date.now() + }); + + + if (cache.size > maxSize){ + const firstKey = cache.keys().next().value; + cache.delete(firstKey); + } + + return result; + } - // Return placeholder that doesn't work - const memoized = function () { - return undefined; - }; memoized.cache = { - clear: () => {}, - delete: () => false, - has: () => false, - get size() { - return -1; - }, + clear: () => cache.clear(), + delete: (...args) => cache.delete(generator(args)), + has: (...args) => cache.has(generator(args)), + get size() { return cache.size; } }; + return memoized; } diff --git a/05-promise-utilities/index.js b/05-promise-utilities/index.js index e59a8eb..f0d61be 100644 --- a/05-promise-utilities/index.js +++ b/05-promise-utilities/index.js @@ -8,30 +8,33 @@ * @returns {Promise} A promise that resolves to an array of results */ function promiseAll(promises) { - // TODO: Implement promiseAll + const promiseArray = Array.from(promises); - // Step 1: Convert iterable to array - // const promiseArray = Array.from(promises); + if (promiseArray.length === 0) return Promise.resolve([]) - // Step 2: Handle empty array case - // Return Promise.resolve([]) for empty input - // Step 3: Create a new Promise - // return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { - // Step 4: Track results and completion count - // const results = new Array(promiseArray.length); - // let completed = 0; + const results = new Array(promiseArray.length); + let resultsCount = 0; + + promiseArray.forEach((promise, index) => { + Promise.resolve(promise) + .then((value) => { + results[index] = value; + + resultsCount++; - // 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 + if (resultsCount === promiseArray.length){ + resolve(results); + } + }) + .catch((error) => { + reject(error); + }) + }); + }); - // }); - - return Promise.reject(new Error("Not implemented")); // Broken: Replace with your implementation } /** @@ -43,19 +46,18 @@ 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 - - // Step 3: Create a new Promise - // The first promise to settle wins - - // Step 4: For each promise, attach then/catch that resolves/rejects the race - return new Promise(() => {}); // Replace with your implementation + const promiseArray = Array.from(promises); + + if (promiseArray.length === 0) return new Promise(() => {}); + + return new Promise((resolve, reject) => { + promiseArray.forEach((promise, index) => { + Promise.resolve(promise) + .then((value) => resolve(value)) + .catch((error) => reject(error)); + }) + }); } /** @@ -68,24 +70,33 @@ 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 + const promiseArray = Array.from(promises); - // Step 2: Handle empty array case + if (promiseArray.length === 0) return Promise.resolve([]); - // Step 3: Create a new Promise + return new Promise((resolve) => { - // Step 4: Track results and completion count - // Each result is: { status: 'fulfilled', value } or { status: 'rejected', reason } + const result = new Array(promiseArray.length); + let settledCount = 0; - // 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 + promiseArray.forEach((promise, index) => { - return Promise.reject(new Error("Not implemented")); // Broken: Replace with your implementation + Promise.resolve(promise) + .then((value) => { + result[index] = { status: 'fulfilled', value: value} + }) + .catch((reason) => { + result[index] = {status: 'rejected', reason: reason} + }) + .finally(() => { + settledCount++; + if (settledCount === promiseArray.length){ + resolve(result); + } + }); + }) + }); } /** @@ -98,27 +109,36 @@ 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 + const promiseArray = Array.from(promises); - // Step 2: Handle empty array (reject with AggregateError) + if (promiseArray.length === 0) return Promise.reject(new AggregateError([], 'Cannot agregate empty array')); - // Step 3: Create a new Promise + return new Promise((resolve, reject) => { - // Step 4: Track rejection count and errors - // const errors = []; - // let rejectedCount = 0; + const errors = new Array(promiseArray.length); + let errorsCount = 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 + let isResolved = false; + promiseArray.forEach((promise, index) => { + Promise.resolve(promise) + .then((value) => { + isResolved = true; + resolve(value); - // Note: AggregateError is created like: - // new AggregateError(errorsArray, 'All promises were rejected') + }) + .catch((error) => { + if (!isResolved){ + errors[index] = error; + errorsCount++; - return Promise.reject(new AggregateError([], "No promises")); // Replace + if (errorsCount === 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..3652329 100644 --- a/06-async-queue/index.js +++ b/06-async-queue/index.js @@ -1,3 +1,5 @@ +const { run } = require("jest"); + /** * Async Queue Implementation * @@ -11,15 +13,14 @@ class AsyncQueue { * @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 = []; // Pending tasks + this.running = 0; // Currently running count + this.paused = false; // Paused state + this.emptyCallbacks = []; // Callbacks for empty event } /** @@ -30,44 +31,41 @@ class AsyncQueue { * @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 + + return new Promise((resolve, reject) => { + const priority = options.priority || 0; + + const taskEntry = {task, priority, resolve, reject}; + + this.queue.push(taskEntry); + this.queue.sort((taskA, taskB) => taskB.priority - taskA.priority); + + if (this.autoStart && !this.paused){ + 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 = []; } /** @@ -75,8 +73,9 @@ class AsyncQueue { * @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 + if (typeof callback === 'function'){ + this.emptyCallbacks.push(callback); + } } /** @@ -84,8 +83,7 @@ class AsyncQueue { * @returns {number} */ get size() { - // TODO: Return queue length - throw new Error("Not implemented"); + return this.queue.length; } /** @@ -93,8 +91,7 @@ class AsyncQueue { * @returns {number} */ get pending() { - // TODO: Return running count - throw new Error("Not implemented"); + return this.running; } /** @@ -102,8 +99,7 @@ class AsyncQueue { * @returns {boolean} */ get isPaused() { - // TODO: Return paused state - throw new Error("Not implemented"); + return this.paused; } /** @@ -111,17 +107,23 @@ class AsyncQueue { * @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 taskEntry = this.queue.shift(); + + this.running++; + + Promise.resolve() + .then(() => taskEntry.task()) + .then(result => taskEntry.resolve(result)) + .catch(error => taskEntry.reject(error)) + .finally(() => { + this.running--; + this._process(); + this._checkEmpty(); + }) + } } /** @@ -129,7 +131,18 @@ class AsyncQueue { * @private */ _checkEmpty() { - // TODO: If queue is empty and nothing running, call empty callbacks + if (this.queue.length === 0 && !this.running){ + + this.emptyCallbacks.forEach((callback) => { + try{ + callback(); + } + catch(error){ + console.error('Error in empty callback:', error); + } + }); + + } } } diff --git a/06-async-queue/index.test.js b/06-async-queue/index.test.js index 04f3dc9..b4029c7 100644 --- a/06-async-queue/index.test.js +++ b/06-async-queue/index.test.js @@ -251,6 +251,8 @@ describe("AsyncQueue", () => { await queue.add(async () => "task"); + await delay(10); + expect(emptyCalled).toBe(true); }); @@ -263,6 +265,8 @@ describe("AsyncQueue", () => { await queue.add(async () => {}); + await delay(10); + expect(calls).toEqual(["callback1", "callback2"]); }); diff --git a/07-retry-with-backoff/index.js b/07-retry-with-backoff/index.js index ed7fd75..0ed3965 100644 --- a/07-retry-with-backoff/index.js +++ b/07-retry-with-backoff/index.js @@ -15,38 +15,40 @@ * @returns {Promise} Result of fn or throws last error */ async function retry(fn, options = {}) { - // TODO: Implement retry with backoff + const { + maxRetries = 3, + initialDelay = 1000, + maxDelay = 30000, + backoff = 'exponential', + jitter = false, + retryIf = () => true, + onRetry = () => {} + } = options; - // 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 + let lastError = null; - // Step 3: Loop up to maxRetries + 1 (initial attempt + retries) + for (let attempt = 0; attempt <= maxRetries; attempt++){ + try{ + const result = await fn(); + return result; + } + catch(error){ - // Step 4: Try to execute fn - // - On success: return result - // - On error: check if should retry + lastError = error; - // 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 + if (attempt === maxRetries || !retryIf(error, attempt+1)) throw lastError; - // Step 6: If all retries exhausted, throw last error + await onRetry(error, attempt+1); + + let delay = calculateDelay(backoff, attempt+1, initialDelay); + delay = Math.min(delay, maxDelay); - throw new Error("Not implemented"); // Replace with your implementation + if (jitter) delay = applyJitter(delay); + + await sleep(delay); + } + } } /** @@ -58,13 +60,15 @@ async function retry(fn, options = {}) { * @returns {number} Calculated delay in ms */ function calculateDelay(strategy, attempt, initialDelay) { - // TODO: Implement delay calculation + switch (strategy){ + case 'fixed': return initialDelay; + + case 'linear': return initialDelay * attempt; - // Fixed: delay = initialDelay - // Linear: delay = initialDelay * attempt - // Exponential: delay = initialDelay * 2^(attempt-1) + case 'exponential': return initialDelay * Math.pow(2, attempt-1); - throw new Error("Not implemented"); + default: return initialDelay; + } } /** @@ -74,10 +78,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..37bd8d1 100644 --- a/07-retry-with-backoff/index.test.js +++ b/07-retry-with-backoff/index.test.js @@ -1,5 +1,11 @@ const { retry, calculateDelay, applyJitter } = require("./index"); +function preventUnhandledRejection(promise) { + promise.catch(() => {}); + return promise; +} + + describe("retry", () => { beforeEach(() => { jest.useFakeTimers(); @@ -44,7 +50,7 @@ describe("retry", () => { const error = new Error("persistent failure"); const fn = jest.fn().mockRejectedValue(error); - const promise = retry(fn, { maxRetries: 2, initialDelay: 100 }); + const promise = preventUnhandledRejection(retry(fn, { maxRetries: 2, initialDelay: 100 })); await jest.advanceTimersByTimeAsync(0); // attempt 1 await jest.advanceTimersByTimeAsync(100); // attempt 2 @@ -59,7 +65,7 @@ describe("retry", () => { test("should respect maxRetries", async () => { const fn = jest.fn().mockRejectedValue(new Error("fail")); - const promise = retry(fn, { maxRetries: 5, initialDelay: 10 }); + const promise = preventUnhandledRejection(retry(fn, { maxRetries: 5, initialDelay: 10 })); for (let i = 0; i < 10; i++) { await jest.advanceTimersByTimeAsync(100); @@ -72,7 +78,7 @@ describe("retry", () => { test("should handle maxRetries of 0", async () => { const fn = jest.fn().mockRejectedValue(new Error("fail")); - const promise = retry(fn, { maxRetries: 0 }); + const promise = preventUnhandledRejection(retry(fn, { maxRetries: 0 })); await jest.advanceTimersByTimeAsync(0); await expect(promise).rejects.toThrow(); @@ -87,10 +93,10 @@ describe("retry", () => { .mockRejectedValueOnce({ status: 500 }) .mockResolvedValueOnce("success"); - const promise = retry(fn, { + const promise = preventUnhandledRejection(retry(fn, { initialDelay: 100, retryIf: (error) => error.status >= 500, - }); + })); await jest.advanceTimersByTimeAsync(100); const result = await promise; @@ -102,9 +108,9 @@ describe("retry", () => { test("should not retry when retryIf returns false", async () => { const fn = jest.fn().mockRejectedValue({ status: 400 }); - const promise = retry(fn, { + const promise = preventUnhandledRejection(retry(fn, { retryIf: (error) => error.status >= 500, - }); + })); await jest.advanceTimersByTimeAsync(0); await expect(promise).rejects.toEqual({ status: 400 }); @@ -192,12 +198,12 @@ describe("retry with backoff strategies", () => { const delays = []; const fn = jest.fn().mockRejectedValue(new Error("fail")); - const promise = retry(fn, { + const promise = preventUnhandledRejection(retry(fn, { maxRetries: 5, initialDelay: 1000, maxDelay: 5000, backoff: "exponential", - }); + })); // Track delays by advancing time // exponential would be: 1000, 2000, 4000, 8000, 16000 diff --git a/08-event-emitter/index.js b/08-event-emitter/index.js index b7c9a7c..6e1873b 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,13 @@ class EventEmitter { * @returns {EventEmitter} this (for chaining) */ on(event, listener) { - // TODO: Implement on - // Step 1: Get or create the listeners array for this event + if (!this.events.get(event)) this.events.set(event, []); - // Step 2: Add the listener to the array + const listeners = this.events.get(event) - // Step 3: Return this for chaining - - return null; // Broken: should return this + listeners.push(listener); + return this; } /** @@ -34,16 +31,22 @@ class EventEmitter { * @returns {EventEmitter} this (for chaining) */ off(event, listener) { - // TODO: Implement off - // Step 1: Get the listeners array for this event + let listeners = this.events.get(event); + + if (!listeners || listeners.length === 0) return this; + + const indexToDel = listeners.findIndex((element) => + element === listener || (element._original && element._original === listener) + ); - // Step 2: Find and remove the listener - // Note: Handle wrapped 'once' listeners + if (indexToDel!=-1){ + listeners.splice(indexToDel, 1); + } - // Step 3: Return this for chaining + if (listeners.length===0) this.events.delete(event); - return null; // Broken: should return this + return this; } /** @@ -53,18 +56,18 @@ class EventEmitter { * @returns {boolean} true if event had listeners */ emit(event, ...args) { - // TODO: Implement emit - // Step 1: Get the listeners array for this event + const listeners = this.events.get(event); - // Step 2: If no listeners, return false + if (!listeners) return false; + + const copyListeners = [...listeners] - // Step 3: Call each listener with the arguments - // Make a copy of the array to handle removals during emit + copyListeners.forEach(listener => { + listener.apply(this, args); + }); - // Step 4: Return true - - throw new Error("Not implemented"); + return true; } /** @@ -76,17 +79,17 @@ class EventEmitter { once(event, listener) { // TODO: Implement once - // Step 1: Create a wrapper function that: - // - Removes itself after being called - // - Calls the original listener with arguments + const wrapper = (...args) => { + this.off(event, wrapper); - // Step 2: Store reference to original listener for 'off' to work + return listener.apply(this, args); + } - // Step 3: Register the wrapper with 'on' + wrapper._original = listener; - // Step 4: Return this for chaining + this.on(event, wrapper); - return null; // Broken: should return this + return this; } /** @@ -95,12 +98,12 @@ 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 +112,8 @@ 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"); + const listeners = this.events.get(event) + return listeners ? [...listeners] : []; } /** @@ -122,9 +122,8 @@ class EventEmitter { * @returns {number} Listener count */ listenerCount(event) { - // TODO: Implement listenerCount - - throw new Error("Not implemented"); + const listeners = this.events.get(event); + return listeners ? listeners.length : 0; } } diff --git a/09-observable/index.js b/09-observable/index.js index a7e8f15..de5ffad 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,40 @@ 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 - - // Step 3: Call the subscribe function with the subscriber - - // Step 4: Handle cleanup function returned by subscribeFn - - // Step 5: Return subscription object with unsubscribe method - - throw new Error("Not implemented"); + if (typeof observer === 'function') observer = {next: observer}; + + let isCompleted = false; + let isError = false; + let isUnsubscribed = false; + + let subscriber = { + next : (value) => { + if (!isCompleted && !isError && !isUnsubscribed){ + observer.next?.(value) + } + }, + error: (error) => { + isError = true; + observer.error?.(error) + }, + complete: () => { + if (!isCompleted && !isError){ + isCompleted = true; + observer.complete?.(); + } + } + } + + const cleanupFn = this._subscribeFn(subscriber); + + return { + unsubscribe: () => { + isUnsubscribed = true; + if (cleanupFn && typeof cleanupFn === 'function'){ + cleanupFn(); + } + } + } } /** @@ -44,14 +60,18 @@ 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(() => {}); // Broken: Replace with implementation + return new Observable((subscriber) => { + const subscription = this.subscribe({ + next: (value) => { + const result = fn(value); + subscriber.next(result); + }, + error: (error) => subscriber.error(error), + complete: () => subscriber.complete() + }); + + return () => subscription.unsubscribe(); + }); } /** @@ -60,13 +80,20 @@ 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) => { + const result = predicate(value); + if (result === true){ + subscriber.next(value); + } + }, + error: error => subscriber.error(error), + complete: () => subscriber.complete() + }); + + return () => subscription.unsubscribe(); + }); } /** @@ -75,14 +102,23 @@ 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 valueLeft = count; + const subscription = this.subscribe({ + next: (value) => { + if (valueLeft!=0){ + valueLeft--; + subscriber.next(value); + } + else subscriber.complete(); + }, + error: error => subscriber.error(error), + complete: () => subscriber.complete() + }); + + return () => subscription.unsubscribe(); + }); } /** @@ -91,14 +127,22 @@ 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 skipLeft = count; + const subscription = this.subscribe({ + next: (value) => { + if (skipLeft<=0){ + subscriber.next(value); + } + else skipLeft--; + }, + error: error => subscriber.error(error), + complete: () => subscriber.complete() + }); + + return () => subscription.unsubscribe(); + }); } /** @@ -107,15 +151,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() + array.forEach((element) => { + subscriber.next(element); + }) + subscriber.complete(); }); } @@ -125,10 +165,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..a3dbe08 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 - - // Step 1: Check if key exists - - // 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"); + + if (this.cache.has(key)){ + const value = this.cache.get(key); + + this.put(key, value); + + return value; + } + + return undefined; } /** @@ -42,10 +37,15 @@ 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(this.has(key)){ + this.delete(key); + } + else if (this.size >= this.capacity){ + this.delete(this.cache.keys().next().value); + } + + this.cache.set(key, value); } /** @@ -54,9 +54,7 @@ class LRUCache { * @returns {boolean} */ has(key) { - // TODO: Implement has - - throw new Error("Not implemented"); + return this.cache.has(key); } /** @@ -65,17 +63,18 @@ class LRUCache { * @returns {boolean} true if key existed */ delete(key) { - // TODO: Implement delete - - throw new Error("Not implemented"); + if (this.has(key)){ + this.cache.delete(key); + return true; + } + return false; } /** * Clear all items */ clear() { - // TODO: Implement clear - throw new Error("Not implemented"); + this.cache = new Map(); } /** @@ -83,9 +82,7 @@ class LRUCache { * @returns {number} */ get size() { - // TODO: Return current size - - throw new Error("Not implemented"); + return this.cache.size; } /** @@ -93,9 +90,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 +98,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..d6adaad 100644 --- a/11-singleton/index.js +++ b/11-singleton/index.js @@ -8,31 +8,27 @@ * A class that only allows one instance to exist. */ class Singleton { - // TODO: Implement Singleton + + static instance = null; - // 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 + constructor(){ + if (Singleton.instance){ + throw new Error('Use Singleton.getInstance()'); + } + } static getInstance() { - // TODO: Implement getInstance - throw new Error("Not implemented"); + if (!this.instance){ + this.instance = new Singleton(); + } + + return this.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 + this.instance = null; } } @@ -45,10 +41,9 @@ class Singleton { * @returns {Object} Object with getInstance method */ function createSingleton(Class) { - // TODO: Implement createSingleton + + let instance = null; - // Step 1: Create a closure variable to hold the instance - // let instance = null; // Step 2: Return an object with getInstance method // getInstance should: @@ -60,11 +55,13 @@ function createSingleton(Class) { 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..39deec3 100644 --- a/12-factory-pattern/index.js +++ b/12-factory-pattern/index.js @@ -61,7 +61,6 @@ class Triangle { * A factory object that creates shapes based on type. */ const ShapeFactory = { - // TODO: Implement create method /** * Create a shape instance @@ -70,12 +69,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 + switch(type){ + case 'circle': return new Circle(options); - return null; // Replace with implementation + case 'rectangle': return new Rectangle(options); + + case 'triangle': return new Triangle(options); + + default: throw new Error('Unknown type for factory'); + } }, }; @@ -86,8 +89,7 @@ const ShapeFactory = { */ class Factory { constructor() { - // TODO: Initialize registry - // this.registry = new Map(); + this.registry = new Map(); } /** @@ -99,8 +101,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 +110,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 +120,27 @@ 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 - // Step 3: Validate required fields (if specified) - - // Step 4: Run custom validation (if specified) - - // Step 5: Create and return instance - - return null; // Replace with implementation + if (this.has(type)){ + const {Class, options} = this.registry.get(type); + + if (options.required){ + for (const key of options.required){ + if (! (key in args)){ + throw new Error('Missing required arguments'); + } + } + } + if (options.validate){ + const isValid = options.validate(args); + if (!isValid){ + throw new Error('Invalid arguments!'); + } + } + + return new Class(args); + } + throw new Error('Unknow type for factory'); } /** @@ -142,9 +149,7 @@ class Factory { * @returns {boolean} */ has(type) { - // TODO: Implement has - - throw new Error("Not implemented"); + return this.registry.has(type); } /** @@ -152,17 +157,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 = new Map(); } } diff --git a/13-decorator-pattern/index.js b/13-decorator-pattern/index.js index 6a3646f..36c57a7 100644 --- a/13-decorator-pattern/index.js +++ b/13-decorator-pattern/index.js @@ -11,22 +11,17 @@ * @returns {Function} Decorated function */ function withLogging(fn) { - // TODO: Implement withLogging + return function(...args) { - // Step 1: Return a new function that wraps fn + console.log(`Function name: ${fn.name}\nFunction arguments: ${args}`); - // Step 2: Log the function name and arguments + const result = fn.apply(this, args); - // Step 3: Call the original function + console.log("Return value: " + result) - // Step 4: Log the return value + return result; - // Step 5: Return the result - - // Note: Preserve 'this' context using apply/call - - // Broken: throws error - throw new Error("Not implemented"); + } } /** @@ -38,19 +33,18 @@ 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()) + return function(...args){ - // Step 3: Call original function + const startTime = Date.now(); + + const result = fn.apply(this, args); - // Step 4: Calculate and log duration + const duration = Date.now() - startTime; + console.log("Duration of function call is " + duration); - // Step 5: Return result - - return () => undefined; // Broken placeholder + return result; + } } /** @@ -63,20 +57,21 @@ 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 + return function(...args){ - // Step 3: Loop up to maxRetries: - // - Try to call fn - // - On success, return result - // - On failure, increment attempts and continue + let lastError = null; - // Step 4: If all retries fail, throw the last error - - return () => undefined; // Broken placeholder + for (let attempt = 0; attempt <= maxRetries; attempt++){ + try{ + return fn.apply(this, args); + } + catch(error){ + lastError = error; + } + } + throw new Error("Cannot apply function because of " + lastError.message); + } } /** @@ -88,11 +83,22 @@ 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 + const cache = new Map(); + + return function(...args){ + + const key = JSON.stringify(args); + + if (cache.has(key)){ + return cache.get(key); + } + + const value = fn.apply(this, args); + cache.set(key, value); - return () => undefined; // Broken placeholder + return value; + } } /** @@ -105,17 +111,20 @@ function withMemoize(fn) { * @returns {Function} Decorated function */ function withValidation(fn, validator) { - // TODO: Implement withValidation - // Step 1: Return a new function + return function(...args){ - // Step 2: Call validator with arguments + if (validator && typeof validator === 'function'){ + + const isValid = validator(...args); + + if (isValid){ + return fn.apply(this, args); + } - // Step 3: If validation fails, throw error - - // Step 4: If passes, call original function - - return () => undefined; // Broken placeholder + throw new Error("Args are not valid"); + } + } } /** @@ -128,19 +137,26 @@ function withValidation(fn, validator) { * @returns {Object} Object with cached method */ function withCache(obj, methodName) { - // TODO: Implement withCache - // Step 1: Get the original method + const originalMethod = obj[methodName]; + const cache = new Map() + + obj[methodName] = function(...args){ - // Step 2: Create a cache (Map) + const key = JSON.stringify(args); - // Step 3: Replace the method with a caching wrapper + if (cache.has(key)){ + return cache.get(key); + } - // Step 4: Return the object + const value = originalMethod.apply(this, args); + cache.set(key, value); + + return value; + } - // Broken: deletes the method instead of caching it - delete obj[methodName]; return obj; + } /** @@ -153,15 +169,12 @@ 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 function(fn){ + return decorators.reduceRight((currentFn, decorator) => { + return decorator(currentFn); + }, fn) + } } /** @@ -173,13 +186,11 @@ 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 function(fn){ + return decorators.reduce((currentFn, decorator) => { + return decorator(currentFn); + }, fn) + } } // Storage for logs (used in tests) @@ -187,7 +198,7 @@ const logs = []; function log(message) { logs.push(message); - // console.log(message); // Uncomment for debugging + console.log(message); // Uncomment for debugging } function clearLogs() { diff --git a/14-middleware-pipeline/index.js b/14-middleware-pipeline/index.js index 8db7620..f27d0fd 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,12 @@ 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 + if (typeof fn === 'function'){ + this.middleware.push(fn); + } - return null; // Broken: should return this + return this; } /** @@ -32,21 +28,27 @@ 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]; + + if (!fn) return Promise.resolve(); - // Step 2: Start dispatch at index 0 + try{ + return Promise.resolve( + fn(context, () => dispatch(index+1)) + ); + } + catch(error){ + return Promise.reject(error); + } - // Step 3: Return promise for async support + } - // Broken: rejects instead of resolving - return Promise.reject(new Error("Not implemented")); + return dispatch(0); } /** @@ -54,10 +56,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 +69,32 @@ 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) + middleware.forEach((fn) => { + if (typeof fn !== 'function'){ + throw new Error('Middleware must be 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 + if (index >= middleware.length) return Promise.resolve(); - // Broken: rejects instead of resolving - return Promise.reject(new Error("Not implemented")); + const fn = middleware[index]; + + if (!fn) return Promise.resolve(); + + function next(){ + return dispatch(index + 1); + } + + try{ + return Promise.resolve(fn(context, next)); + } + catch(error){ + return Promise.reject(error); + } } return dispatch(0); @@ -106,16 +109,15 @@ 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 async function(context, next){ - return (ctx, next) => { - throw new Error("Not implemented"); - }; + if (condition(context)){ + return middleware(context, next); + } + + return next(); + } } /** @@ -125,15 +127,16 @@ 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 function(context, next){ - return async (ctx, next) => { - throw new Error("Not implemented"); - }; + try{ + await next(); + } + catch(error){ + await errorHandler(error, context); + } + } } module.exports = { diff --git a/15-dependency-injection/index.js b/15-dependency-injection/index.js index e4995b5..d956332 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,15 @@ 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 +32,11 @@ 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 +47,15 @@ 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 + }) + } /** @@ -54,30 +67,42 @@ class Container { resolve(name, resolutionStack = new Set()) { // TODO: Implement resolve - // Step 1: Check if service is registered - // Throw error if not found + const service = this.registry.get(name) + if (!service) throw new Error('Service is not registered'); + + if (resolutionStack.has(name)) throw new Error('Service circular dependencies error'); + + switch (service.type){ + case 'instance': + return service.instance; + + case 'class': + case 'factory': + if (service.singleton && service.instance) return service.instance; + + resolutionStack.add(name); - // Step 2: Check for circular dependencies - // If name is already in resolutionStack, throw error + const dependencies = service.dependencies.map((depName) => { + return this.resolve(depName, resolutionStack); + }); - // Step 3: Get registration from registry + let instance = null; - // Step 4: Handle different types: + if (service.type === 'class'){ + instance = new service.Class(...dependencies); + } + else { + instance = service.factory(...dependencies); + } - // For 'instance': - // - Return the stored instance + resolutionStack.delete(name); - // 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 + if (service.singleton){ + service.instance = instance; + } - // Broken: returns undefined (causes test assertions to fail) - return undefined; + return instance; + } } /** @@ -86,8 +111,7 @@ class Container { * @returns {boolean} */ has(name) { - // TODO: Implement has - throw new Error("Not implemented"); + return this.registry.has(name); } /** @@ -96,16 +120,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 = new Map(); } /** @@ -113,8 +135,7 @@ class Container { * @returns {string[]} */ getRegistrations() { - // TODO: Implement getRegistrations - throw new Error("Not implemented"); + return Array.from(this.registry.keys()); } } @@ -125,14 +146,21 @@ class Container { * @returns {Container} Child container */ function createChildContainer(parent) { - // TODO: Implement createChildContainer - - // Create a new container that: - // - First checks its own registry - // - Falls back to parent for unregistered services const child = new Container(); - // Override resolve to check parent... + + const origResolve = child.resolve.bind(child); + + child.resolve = function(name, resolutionStack = new Set()){ + + if (this.registry.has(name)){ + return origResolve(name, resolutionStack); + } + + return parent.resolve(name, resolutionStack); + + } + return child; } diff --git a/16-state-machine/index.js b/16-state-machine/index.js index d0bad18..2d279aa 100644 --- a/16-state-machine/index.js +++ b/16-state-machine/index.js @@ -10,13 +10,22 @@ 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) { + throw new Error('Config must have initial state'); + } + + if (!config.states || typeof config.states !== 'object') { + throw new Error('Config must have states object'); + } + + this.config = config; + this.currentState = config.initial; + this.context = config.context || {}; + + if (!(this.currentState in config.states)){ + throw new Error('Initial state is not in states object!'); + } } /** @@ -24,8 +33,7 @@ class StateMachine { * @returns {string} */ get state() { - // TODO: Return current state - throw new Error("Not implemented"); + return this.currentState; } /** @@ -35,27 +43,38 @@ class StateMachine { * @returns {boolean} Whether transition was successful */ transition(event, payload) { - // TODO: Implement transition - // Step 1: Get current state config + const currentStateConfig = this.config.states[this.currentState]; - // Step 2: Check if event is valid for current state - // Return false if not + const trans = currentStateConfig?.on?.[event]; + if (!trans){ + return false; + } - // Step 3: Get transition config (can be string or object) - // If string: target = transition - // If object: { target, guard, action } + let target, guard, action; - // Step 4: Check guard if present - // If guard returns false, return false + if (typeof trans === 'string'){ + target = trans; + } + else if (typeof trans === 'object'){ + target = trans.target; + guard = trans.guard; + action = trans.action; + } + else return false; - // Step 5: Update state to target - // Step 6: Call action if present - // Step 7: Return true + if (guard && !guard(this.context, payload)){ + return false; + } - throw new Error("Not implemented"); + const prevState = this.currentState; + this.currentState = target; + + if (action) action(this.context, payload); + + return true; } /** @@ -64,12 +83,17 @@ class StateMachine { * @returns {boolean} */ can(event) { - // TODO: Implement can + const stateConfig = this.config.states[this.currentState]; + + const transition = stateConfig?.on?.[event]; + + if (!transition) return false; - // Check if event exists for current state - // Check guard if present + if (typeof transition === 'object' && transition.guard){ + return transition.guard(this.context); + } - throw new Error("Not implemented"); + return true; } /** @@ -77,11 +101,12 @@ 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 []; + + return Object.keys(stateConfig.on); - throw new Error("Not implemented"); } /** @@ -89,8 +114,7 @@ class StateMachine { * @returns {Object} */ getContext() { - // TODO: Return context - throw new Error("Not implemented"); + return this.context; } /** @@ -98,9 +122,15 @@ 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 if (typeof updater === 'object' && updater !== null){ + this.context = { ...this.context, ...updater}; + } + else{ + throw new Error('Invalid updater (must be function or object)'); + } } /** @@ -108,8 +138,13 @@ 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]; + if (!stateConfig) return false; + + const trans = stateConfig.on; + + return !trans || Object.keys(trans).length === 0; + } /** @@ -117,8 +152,9 @@ 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) this.context = newContext; } } @@ -129,11 +165,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..b78a765 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,12 @@ 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 +31,18 @@ class CommandManager { * @returns {boolean} Whether undo was performed */ undo() { - // TODO: Implement undo - // Step 1: Check if undo stack is empty + if (this.undoStack.length !== 0){ + const command = this.undoStack.pop(); - // Step 2: Pop command from undo stack + command.undo(); - // Step 3: Call command.undo() + this.redoStack.push(command); - // Step 4: Push to redo stack + return true; + } - // Step 5: Return true - - throw new Error("Not implemented"); + return false; } /** @@ -50,19 +50,18 @@ 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 + if (this.redoStack.length !== 0){ + const command = this.redoStack.pop(); - // Step 3: Call command.execute() + command.execute(); - // Step 4: Push to undo stack + this.undoStack.push(command); - // Step 5: Return true + return true; + } - throw new Error("Not implemented"); + return false; } /** @@ -70,8 +69,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 +77,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 +85,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 +102,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 +121,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 +140,19 @@ class SubtractCommand { */ class MultiplyCommand { constructor(calculator, value) { - // TODO: Store calculator, value, and previous value for undo + this.calculator = calculator; + this.value = value; + this.prevValue = calculator.value; this.description = `Multiply by ${value}`; } execute() { - // TODO: Multiply calculator.value by value - // Save previous value for undo + this.prevValue = this.calculator.value; + this.calculator.value *= this.value; } undo() { - // TODO: Restore previous value + this.calculator.value = this.prevValue; } } @@ -162,17 +161,19 @@ class MultiplyCommand { */ class DivideCommand { constructor(calculator, value) { - // TODO: Store calculator, value, and previous value for undo + this.calculator = calculator; + this.value = value; + this.prevValue = calculator.value; this.description = `Divide by ${value}`; } execute() { - // TODO: Divide calculator.value by value - // Save previous value for undo + this.prevValue = this.calculator.value; + this.calculator.value /= this.value; } undo() { - // TODO: Restore previous value + this.calculator.value = this.prevValue; } } @@ -183,8 +184,7 @@ class DivideCommand { */ class MacroCommand { constructor(commands = []) { - // TODO: Store commands array - // this.commands = commands; + this.commands = commands; this.description = "Macro"; } @@ -193,15 +193,19 @@ class MacroCommand { * @param {Object} command */ add(command) { - // TODO: Add command to array + this.commands.push(command); } execute() { - // TODO: Execute all commands in order + this.commands.forEach((command) => { + 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 +216,19 @@ class MacroCommand { */ class SetValueCommand { constructor(calculator, value) { - // TODO: Store calculator, new value, and previous value + this.calculator = calculator; + this.value = value; + this.prevValue = calculator.value; this.description = `Set to ${value}`; } execute() { - // TODO: Save previous, set new value + this.prevValue = this.calculator.value; + this.calculator.value = this.value; } undo() { - // TODO: Restore previous value + this.calculator.value = this.prevValue; } } diff --git a/18-strategy-pattern/index.js b/18-strategy-pattern/index.js index 807bcd2..df6acac 100644 --- a/18-strategy-pattern/index.js +++ b/18-strategy-pattern/index.js @@ -13,18 +13,16 @@ */ 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"); + const copy = [...array]; + return this.strategy.sort(copy); } } @@ -33,10 +31,15 @@ class SortContext { */ class BubbleSort { sort(array) { - // TODO: Implement bubble sort - // Return new sorted array - - return ["NOT_IMPLEMENTED"]; // Broken: Replace with implementation + for (let i = 0; i < array.length; i++){ + for (let j = 0; j < array.length-1; j++){ + if (array[j] > array[j+1]){ + [array[j], array[j+1]] = [array[j+1], array[j]]; + } + } + } + + return array; } } @@ -45,10 +48,23 @@ 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 left = []; + const right = []; + const pivot = array[0]; + + for (let i = 1; i < array.length; i++){ + if (array[i] < pivot){ + left.push(array[i]); + } + else{ + right.push(array[i]); + } + } + + return [...this.sort(left), pivot, ...this.sort(right)]; } } @@ -57,10 +73,33 @@ class QuickSort { */ class MergeSort { sort(array) { - // TODO: Implement merge sort - // Return new sorted array + if (array.length <= 1) return array; + + const midIndex = Math.floor(array.length / 2); + + const left = this.sort(array.slice(0, midIndex)); + const right = this.sort(array.slice(midIndex)); - return []; // Broken: Replace with implementation + return this.merge(left, right); + } + + merge(left, right){ + const result = []; + + let i = 0, j = 0; + + while (i { + return sum += item.price; + }, 0); } } @@ -103,14 +142,13 @@ 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"); + return items.reduce((sum, item) => { + return sum += item.price * (1 - this.percentage/100); + }, 0); } } @@ -119,14 +157,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 subtotal = items.reduce((sum, item) => sum + item.price, 0); + return Math.max(subtotal - this.amount, 0); } } @@ -135,9 +171,15 @@ 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 result = 0; + + for (let i = 0; i < sorted.length; i++){ + if (i%2 === 0) result += sorted[i].price; + } + + return result; } } @@ -148,14 +190,24 @@ 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) => a.threshold - b.threshold); } calculate(items) { - // TODO: Apply tier discount based on subtotal - throw new Error("Not implemented"); + + const subtotal = items.reduce((sum, item) => { + return sum += item.price; + }, 0); + + let discount = 0; + + for (const tier of this.tiers){ + if (subtotal >= tier.threshold){ + discount = tier.discount; + } + } + + return subtotal * (1- discount/100); } } @@ -168,16 +220,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); } } @@ -191,10 +242,30 @@ class ValidationContext { */ class StrictValidation { validate(data) { - // TODO: Validate that name, email, and age are all present and valid - // Return { valid: boolean, errors: string[] } - // Example: { valid: false, errors: ["Name is required", "Email is required"] } - throw new Error("Not implemented"); + const name = data.name; + const email = data.email; + const age = data.age; + + const errors = []; + + let valid = true; + + if (!name){ + errors.push('Name is required'); + valid = false; + } + + if (!email){ + errors.push('Email is required'); + valid = false; + } + + if (!Number.isInteger(age)){ + errors.push('Age must be a number'); + valid = false; + } + + return {valid, errors}; } } @@ -206,9 +277,7 @@ class StrictValidation { */ class LenientValidation { validate(data) { - // TODO: Always return valid: true, errors: [] - // This strategy has no validation rules - return { valid: false, errors: ["Not implemented"] }; // Broken: Replace with implementation + return { valid: true, errors: [] }; } } @@ -223,22 +292,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..6688916 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) + const validator = validators[prop]; + + if (validator && !validator(value)) throw new Error('Invalid value for this 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,28 @@ 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"); + logger('get', prop, obj[prop]); + return obj[prop]; }, 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"); + logger('has', prop, undefined); + return prop in obj; }, }); } @@ -78,24 +69,28 @@ 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 + if (methodNames.includes(prop) && typeof obj[prop] === 'function'){ + return function(...args){ + const key = JSON.stringify(args); - // Otherwise, return property normally + if (cache.has(key)){ + return cache.get(key); + } - throw new Error("Not implemented"); + const value = obj[prop](...args); + cache.set(key, value); + + return value; + } + } + + return obj[prop]; }, }); } @@ -110,29 +105,31 @@ 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)){ + return obj[prop]; + } + throw new Error(`${prop} is not readable`); }, set(obj, prop, value) { - // TODO: Check if prop is in writable - // Throw if not allowed - // Broken: doesn't actually set - return true; + if (writable.includes(prop)){ + obj[prop] = value; + return true; + } + throw new Error(`${prop} is not writable`); }, deleteProperty(obj, prop) { - // TODO: Only allow if in writable - // Broken: doesn't delete - return true; + if (writable.includes(prop)){ + delete(obj[prop]); + return true; + } + return false; }, }); } @@ -144,7 +141,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 +149,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 +176,20 @@ 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]; + onChange(prop, value, oldValue); + obj[prop] = value; + 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..b52c8dd 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,39 @@ 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"); + + if (this.selectCols.length === 0){ + throw new Error('SELECT columns required'); + } + if (!this.fromTable){ + throw new Error('FROM table required'); + } + + let query = `SELECT ${this.selectCols.join(', ')} FROM ${this.fromTable}`; + + if (this.whereClauses.length > 0){ + const whereStr = this.whereClauses + .map(w => `${w.column} ${w.operator} ${JSON.stringify(w.value)}`) + .join(' AND '); + + query += ` WHERE ${whereStr}`; + + } + + if (this.orderByClauses.length > 0){ + const orderStr = this.orderByClauses + .map(o => `${o.column} ${o.direction}`) + .join(', '); + + query += ` ORDER BY ${orderStr}`; + } + + if (this.limitCount !== null){ + query += ` LIMIT ${this.limitCount}`; + } + + + return query; } /** @@ -85,8 +114,13 @@ 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 +131,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 +145,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ tag(name) { - // TODO: Store tag name - throw new Error("Not implemented"); + this.tagName = name; + return this; } /** @@ -122,8 +155,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ id(id) { - // TODO: Store id - throw new Error("Not implemented"); + this.idAttr = id; + return this; } /** @@ -132,8 +165,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ class(...classNames) { - // TODO: Store classes - throw new Error("Not implemented"); + this.classes.push(...classNames); + return this; } /** @@ -143,8 +176,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 +186,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ content(content) { - // TODO: Store content - throw new Error("Not implemented"); + this.innerContent = content; + return this; } /** @@ -163,8 +196,8 @@ class HTMLBuilder { * @returns {HTMLBuilder} this */ child(childHtml) { - // TODO: Store child - throw new Error("Not implemented"); + this.children.push(childHtml); + return this; } /** @@ -172,9 +205,28 @@ class HTMLBuilder { * @returns {string} HTML element string */ build() { - // TODO: Build and return HTML string - // Format: content - throw new Error("Not implemented"); + const attributes = []; + + if (this.idAttr){ + attributes.push(`id="${this.idAttr}"`); + } + if (this.classes.length>0){ + attributes.push(`class="${this.classes.join(' ')}"`); + } + + for (const [name, value] of Object.entries(this.attributes)){ + attributes.push(`${name}="${value}"`); + } + + const children = this.children.join(''); + + const isVoidElement = ['img', 'br', 'hr', 'input', 'meta'].includes(this.tagName); + + if (isVoidElement) return `<${this.tagName}${attributes.length ? ' ' + attributes.join(' ') : ''}>`; + + const content = this.innerContent + children; + + return `<${this.tagName}${attributes.length ? ' ' + attributes.join(' ') : ''}>${content}`; } /** @@ -182,8 +234,14 @@ 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 +252,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 +266,8 @@ class ConfigBuilder { * @returns {ConfigBuilder} this */ setEnvironment(env) { - // TODO: Set environment - throw new Error("Not implemented"); + this.config.environment = env; + return this; } /** @@ -219,8 +276,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 +286,8 @@ class ConfigBuilder { * @returns {ConfigBuilder} this */ enableFeature(feature) { - // TODO: Add feature to list - throw new Error("Not implemented"); + this.config.features.push(feature); + return this; } /** @@ -239,8 +296,11 @@ class ConfigBuilder { * @returns {ConfigBuilder} this */ disableFeature(feature) { - // TODO: Remove feature from list - throw new Error("Not implemented"); + const featureIndex = this.config.features.findIndex(f => f === feature); + if (featureIndex!=-1){ + this.config.features.splice(featureIndex, 1); + } + return this; } /** @@ -249,8 +309,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 +318,7 @@ class ConfigBuilder { * @returns {Object} Configuration object */ build() { - // TODO: Return copy of config - throw new Error("Not implemented"); + return structuredClone(this.config); } } @@ -270,7 +329,14 @@ class ConfigBuilder { */ class RequestBuilder { constructor(baseUrl = "") { - // TODO: Initialize state + this.baseUrl = baseUrl; + this.config = { + method: 'GET', + path: '', + query: {}, + headers: {}, + body: null + } } /** @@ -279,7 +345,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ method(method) { - throw new Error("Not implemented"); + this.config.method = method; + return this; } /** @@ -288,7 +355,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ path(path) { - throw new Error("Not implemented"); + this.config.path = path; + return this; } /** @@ -298,7 +366,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ query(key, value) { - throw new Error("Not implemented"); + this.config.query[key] = value; + return this; } /** @@ -308,7 +377,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ header(key, value) { - throw new Error("Not implemented"); + this.config.headers[key] = value; + return this; } /** @@ -317,7 +387,8 @@ class RequestBuilder { * @returns {RequestBuilder} this */ body(body) { - throw new Error("Not implemented"); + this.config.body = body; + return this; } /** @@ -325,8 +396,18 @@ class RequestBuilder { * @returns {Object} Request config for fetch */ build() { - // TODO: Return fetch-compatible config - throw new Error("Not implemented"); + const url = new URL(this.config.path, this.baseUrl || 'http://localhost'); + + Object.entries(this.config.query).forEach(([key, value]) => { + url.searchParams.append(key, value); + }); + + return { + url: url.toString(), + method: this.config.method, + headers: this.config.headers, + body: this.config.body + } } }