From 6a16421f7f5b6ac9d523ae81cd0233acd107e7c4 Mon Sep 17 00:00:00 2001 From: "@Oleh_Ottisko" <144347689+OlehOttisko@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:47:35 +0400 Subject: [PATCH 1/8] Update 1-object.js --- JavaScript/1-object.js | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/JavaScript/1-object.js b/JavaScript/1-object.js index 1685232..5a5863f 100644 --- a/JavaScript/1-object.js +++ b/JavaScript/1-object.js @@ -1,16 +1,25 @@ 'use strict'; -const rome = { name: 'Rome' }; +// Робимо спільний об'єкт `rome` незмінним. +const rome = Object.freeze({ name: 'Rome' }); +// Створюємо об'єкт `marcus` одразу з потрібними даними, без подальших мутацій. const marcus = { id: 1, - name: 'Marcus', + name: 'Marcus Aurelius', city: rome, email: 'marcus@metarhia.com', }; -marcus.name = 'Marcus Aurelius'; -const lucius = Object.assign({}, marcus, { name: 'Lucius Verus' }); -lucius.email = 'lucius@metarhia.com'; +// Створюємо `lucius` на основі `marcus` за допомогою spread-синтаксису +const lucius = { + ...marcus, + name: 'Lucius Verus', + email: 'lucius@metarhia.com', +}; + +// Можна "заморозити" і фінальні об'єкти +// Object.freeze(marcus); +// Object.freeze(lucius); console.log({ marcus, lucius }); From 3aa40902d4ad52b94552cf3bede52e0c3ff53511 Mon Sep 17 00:00:00 2001 From: "@Oleh_Ottisko" <144347689+OlehOttisko@users.noreply.github.com> Date: Thu, 12 Jun 2025 16:55:58 +0400 Subject: [PATCH 2/8] Update 2-record.js --- JavaScript/2-record.js | 75 +++++++++++++++++++++++++++++++----------- 1 file changed, 55 insertions(+), 20 deletions(-) diff --git a/JavaScript/2-record.js b/JavaScript/2-record.js index 8fe6821..0027523 100644 --- a/JavaScript/2-record.js +++ b/JavaScript/2-record.js @@ -10,24 +10,40 @@ class Record { } static #build(fields, isMutable) { + const fieldSet = new Set(fields); + class Struct { - static fields = fields.slice(); + static fields = Object.freeze(fields.slice()); static mutable = isMutable; - static create(...values) { - if (fields.length !== values.length) { - throw new Error('Record arity mismatch'); + // Приймаємо один об'єкт з іменованими аргументами + static create(props) { + for (const field of fields) { + if (!Reflect.has(props, field)) { + throw new Error(`Missing field: ${field}`); + } } - const obj = Object.create(null); - for (let i = 0; i < fields.length; i++) { - obj[fields[i]] = values[i]; + + // Перевірка на зайві поля (опціонально) + for (const key in props) { + if (!fieldSet.has(key)) { + throw new Error(`Unexpected field: ${key}`); + } } + + // Створюємо об'єкт більш декларативно + const obj = Object.fromEntries( + fields.map(field => [field, props[field]]) + ); + + // Залишаємо вихідну логіку "заморозки" return isMutable ? Object.seal(obj) : Object.freeze(obj); } } return Struct; } + // Функція використання update має бути усвідомленим static update(instance, updates) { if (Object.isFrozen(instance)) { throw new Error('Cannot mutate immutable Record'); @@ -39,23 +55,42 @@ class Record { } return instance; } - + static fork(instance, updates) { - const copy = Object.create(null); - for (const key of Object.keys(instance)) { - copy[key] = Reflect.has(updates, key) ? updates[key] : instance[key]; - } + const copy = { ...instance, ...updates }; return Object.isFrozen(instance) ? Object.freeze(copy) : Object.seal(copy); } } -// Usage +// Оновлений приклад використання const City = Record.immutable(['name']); -const User = Record.mutable(['id', 'name', 'city', 'email']); -const rome = City.create('Rome'); -const marcus = User.create(1, 'Marcus', rome, 'marcus@metarhia.com'); -Record.update(marcus, { name: 'Marcus Aurelius' }); -const lucius = Record.fork(marcus, { name: 'Lucius Verus' }); -Record.update(lucius, { email: 'lucius@metarhia.com' }); -console.log({ marcus, lucius }); +// Зробимо User також імутабельним для кращої практики +const User = Record.immutable(['id', 'name', 'city', 'email']); + +// Створюємо екземпляри за допомогою іменованих полів — це набагато чистіше +const rome = City.create({ name: 'Rome' }); + +const marcus = User.create({ + id: 1, + name: 'Marcus', + city: rome, + email: 'marcus@metarhia.com' +}); + +// Замість мутації (update), створюємо нову версію об'єкта через fork +const marcusUpdated = Record.fork(marcus, { name: 'Marcus Aurelius' }); + +const lucius = Record.fork(marcusUpdated, { + name: 'Lucius Verus', + email: 'lucius@metarhia.com' +}); + +console.log({ marcus, marcusUpdated, lucius }); + +// Спроба оновити імутабельний об'єкт викличе помилку +try { + Record.update(marcus, { name: 'FAIL' }); +} catch (err) { + console.error('\nError trying to update immutable record:', err.message); +} From 6ce409f1e17c2b857a0f51563e82d8ef3d716467 Mon Sep 17 00:00:00 2001 From: "@Oleh_Ottisko" <144347689+OlehOttisko@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:03:42 +0400 Subject: [PATCH 3/8] Update 3-branch.js --- JavaScript/3-branch.js | 80 +++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 25 deletions(-) diff --git a/JavaScript/3-branch.js b/JavaScript/3-branch.js index 35630d6..7f1a239 100644 --- a/JavaScript/3-branch.js +++ b/JavaScript/3-branch.js @@ -1,6 +1,7 @@ 'use strict'; class Record { + static immutable(fields) { return Record.#build(fields, false); } @@ -10,27 +11,40 @@ class Record { } static #build(fields, isMutable) { + const fieldSet = new Set(fields); + class Struct { - static fields = fields.slice(); + static fields = Object.freeze(fields.slice()); static mutable = isMutable; - static create(...values) { - if (fields.length !== values.length) { - throw new Error('Record arity mismatch'); - } + + // Приймаємо об'єкт з іменованими полями замість позиційних аргументів. + + static create(props) { const obj = Object.create(null); - for (let i = 0; i < fields.length; i++) { - obj[fields[i]] = values[i]; + for (const field of fields) { + if (!Reflect.has(props, field)) { + throw new Error(`Missing field: ${field}`); + } + obj[field] = props[field]; + } + + for (const key in props) { + if (!fieldSet.has(key)) { + throw new Error(`Unexpected field: ${key}`); + } } + return isMutable ? Object.seal(obj) : Object.freeze(obj); } } return Struct; } + // Оновлює мутабельний екземпляр. Залишається без змін, логіка коректна. static update(instance, updates) { if (Object.isFrozen(instance)) { - throw new Error('Cannot mutate immutable Record'); + throw new Error('Cannot mutate an immutable Record instance'); } for (const key of Object.keys(updates)) { if (Reflect.has(instance, key)) { @@ -40,26 +54,42 @@ class Record { return instance; } + //Створює новий, незалежний екземпляр (імутабельне оновлення). + static fork(instance, updates) { - const obj = Object.create(null); - for (const key of Object.keys(instance)) { - obj[key] = Reflect.has(updates, key) ? updates[key] : instance[key]; - } - return Object.isFrozen(instance) ? Object.freeze(obj) : Object.seal(obj); + const newInstance = { ...instance, ...updates }; + return Object.isFrozen(instance) + ? Object.freeze(newInstance) + : Object.seal(newInstance); } - static branch(instance, updates) { - const obj = Object.create(instance); - for (const key of Object.keys(updates)) { - Reflect.defineProperty(obj, key, { - value: updates[key], - writable: true, - configurable: true, - enumerable: true, - }); - } - return Object.isFrozen(instance) ? Object.freeze(obj) : Object.seal(obj); - } +// Метод `branch` видалено. + } module.exports = { Record }; + +// Приклад використання оптимізованого коду + +const User = Record.immutable(['id', 'name', 'email']); + +const user1 = User.create({ + id: 1, + name: 'Marcus', + email: 'marcus@metarhia.com', +}); + +const user2 = Record.fork(user1, { name: 'Marcus Aurelius' }); + +// `user1` залишився незмінним +const user3 = Record.fork(user2, { email: 'm.aurelius@rome.com', name: 'Marcus Aurelius Antoninus' }); + +console.log('User 1:', user1); +console.log('User 2:', user2); +console.log('User 3:', user3); + +try { + Record.update(user1, { name: 'FAIL' }); +} catch (e) { + console.error('\nSuccessfully caught error:', e.message); +} From ed61b7fd675bf8c41ac9fb2ac00f34fb045190c6 Mon Sep 17 00:00:00 2001 From: "@Oleh_Ottisko" <144347689+OlehOttisko@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:12:36 +0400 Subject: [PATCH 4/8] Update 4-default.js --- JavaScript/4-default.js | 148 ++++++++++++++++++---------------------- 1 file changed, 68 insertions(+), 80 deletions(-) diff --git a/JavaScript/4-default.js b/JavaScript/4-default.js index 7485543..cef4e9e 100644 --- a/JavaScript/4-default.js +++ b/JavaScript/4-default.js @@ -1,6 +1,31 @@ 'use strict'; +function getType(value) { + if (Array.isArray(value)) return 'array'; + if (value === null) return 'null'; + return typeof value; +} + +function validateTypes(props, defaults) { + for (const key in props) { + if (Reflect.has(defaults, key)) { + const defaultValue = defaults[key]; + const newValue = props[key]; + const expectedType = getType(defaultValue); + const actualType = getType(newValue); + if (expectedType !== actualType) { + throw new TypeError( + `Invalid type for "${key}": expected ${expectedType}, got ${actualType}` + ); + } + } + } +} + + class Record { + + static immutable(defaults) { return Record.#build(defaults, false); } @@ -10,104 +35,67 @@ class Record { } static #build(defaults, isMutable) { + // Заморожуємо структуру "класу", щоб уникнути її випадкової зміни const fields = Object.keys(defaults); - const defaultValues = Object.create(null); - for (const key of fields) { - defaultValues[key] = defaults[key]; - } + const frozenDefaults = Object.freeze({ ...defaults }); class Struct { - static fields = fields; - static defaults = defaultValues; + static fields = Object.freeze(fields); + static defaults = frozenDefaults; static mutable = isMutable; - static create(data = {}) { - const obj = Object.create(null); - - for (const key of fields) { - const base = defaultValues[key]; - const value = key in data ? data[key] : base; - - if (!Record.#sameType(base, value)) { - const exp = Record.#typeof(base); - const act = Record.#typeof(value); - throw new TypeError( - `Invalid type for "${key}": expected ${exp}, got ${act}`, - ); - } - - obj[key] = value; - } - - return isMutable ? Object.seal(obj) : Object.freeze(obj); + validateTypes(data, frozenDefaults); + + // Створюємо екземпляр декларативно, поєднуючи `defaults` і `data` + const newInstance = { ...frozenDefaults, ...data }; + return isMutable ? Object.seal(newInstance) : Object.freeze(newInstance); } } - return Struct; } - static #typeof(value) { - if (Array.isArray(value)) return 'array'; - if (value === null) return 'null'; - return typeof value; - } - - static #sameType(a, b) { - if (Array.isArray(a)) return Array.isArray(b); - if (a === null) return b === null; - return typeof a === typeof b; - } - - static #validate(instance, updates) { - for (const key of Object.keys(updates)) { - if (!Reflect.has(instance, key)) continue; - const current = instance[key]; - const next = updates[key]; - if (!Record.#sameType(current, next)) { - const exp = Record.#typeof(current); - const act = Record.#typeof(next); - throw new TypeError( - `Invalid type for "${key}": expected ${exp}, got ${act}`, - ); - } - } - } - static update(instance, updates) { if (Object.isFrozen(instance)) { - throw new Error('Cannot mutate immutable Record'); - } - Record.#validate(instance, updates); - for (const key of Object.keys(updates)) { - if (Reflect.has(instance, key)) { - instance[key] = updates[key]; - } + throw new Error('Cannot mutate an immutable Record'); } + validateTypes(updates, instance); + Object.assign(instance, updates); return instance; } static fork(instance, updates) { - Record.#validate(instance, updates); - const obj = Object.create(null); - for (const key of Object.keys(instance)) { - obj[key] = Reflect.has(updates, key) ? updates[key] : instance[key]; - } - return Object.isFrozen(instance) ? Object.freeze(obj) : Object.seal(obj); - } - - static branch(instance, updates) { - Record.#validate(instance, updates); - const obj = Object.create(instance); - for (const key of Object.keys(updates)) { - Reflect.defineProperty(obj, key, { - value: updates[key], - writable: true, - configurable: true, - enumerable: true, - }); - } - return Object.isFrozen(instance) ? Object.freeze(obj) : Object.seal(obj); + validateTypes(updates, instance); + const newInstance = { ...instance, ...updates }; + return Object.isFrozen(instance) + ? Object.freeze(newInstance) + : Object.seal(newInstance); } + + // Метод `branch` видалено. } module.exports = { Record }; + +// Приклад використання оптимізованого коду + +const User = Record.immutable({ + id: 0, + name: 'Guest', + email: null, + roles: ['guest'], +}); + +const user1 = User.create({ + id: 1, + name: 'Marcus', +}); + +console.log('User 1 (defaults applied):', user1); +const user2 = Record.fork(user1, { email: 'marcus@rome.com' }); +console.log('User 2 (forked):', user2); +console.log('User 1 (remains unchanged):', user1); +try { + User.create({ id: 2, name: 'Lucius', roles: 'admin' }); // roles має бути масивом +} catch (e) { + console.error('\nSuccessfully caught type error:', e.message); +} From 153ea1d46970c6e9ae0911f2f41bffef6c3e1cc5 Mon Sep 17 00:00:00 2001 From: "@Oleh_Ottisko" <144347689+OlehOttisko@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:13:56 +0400 Subject: [PATCH 5/8] Update 1-object.js --- JavaScript/1-object.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/JavaScript/1-object.js b/JavaScript/1-object.js index 5a5863f..d4f77a8 100644 --- a/JavaScript/1-object.js +++ b/JavaScript/1-object.js @@ -19,7 +19,4 @@ const lucius = { }; // Можна "заморозити" і фінальні об'єкти -// Object.freeze(marcus); -// Object.freeze(lucius); - console.log({ marcus, lucius }); From 0d629128dcfa16e6503aae86b8859a4c0c7f4e51 Mon Sep 17 00:00:00 2001 From: "@Oleh_Ottisko" <144347689+OlehOttisko@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:16:00 +0400 Subject: [PATCH 6/8] Update 2-record.js --- JavaScript/2-record.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/JavaScript/2-record.js b/JavaScript/2-record.js index 0027523..9359ac0 100644 --- a/JavaScript/2-record.js +++ b/JavaScript/2-record.js @@ -15,35 +15,26 @@ class Record { class Struct { static fields = Object.freeze(fields.slice()); static mutable = isMutable; - - // Приймаємо один об'єкт з іменованими аргументами static create(props) { for (const field of fields) { if (!Reflect.has(props, field)) { throw new Error(`Missing field: ${field}`); } } - - // Перевірка на зайві поля (опціонально) for (const key in props) { if (!fieldSet.has(key)) { throw new Error(`Unexpected field: ${key}`); } } - // Створюємо об'єкт більш декларативно const obj = Object.fromEntries( fields.map(field => [field, props[field]]) ); - - // Залишаємо вихідну логіку "заморозки" return isMutable ? Object.seal(obj) : Object.freeze(obj); } } return Struct; } - - // Функція використання update має бути усвідомленим static update(instance, updates) { if (Object.isFrozen(instance)) { throw new Error('Cannot mutate immutable Record'); @@ -65,12 +56,8 @@ class Record { // Оновлений приклад використання const City = Record.immutable(['name']); -// Зробимо User також імутабельним для кращої практики const User = Record.immutable(['id', 'name', 'city', 'email']); - -// Створюємо екземпляри за допомогою іменованих полів — це набагато чистіше const rome = City.create({ name: 'Rome' }); - const marcus = User.create({ id: 1, name: 'Marcus', @@ -78,9 +65,7 @@ const marcus = User.create({ email: 'marcus@metarhia.com' }); -// Замість мутації (update), створюємо нову версію об'єкта через fork const marcusUpdated = Record.fork(marcus, { name: 'Marcus Aurelius' }); - const lucius = Record.fork(marcusUpdated, { name: 'Lucius Verus', email: 'lucius@metarhia.com' @@ -88,7 +73,6 @@ const lucius = Record.fork(marcusUpdated, { console.log({ marcus, marcusUpdated, lucius }); -// Спроба оновити імутабельний об'єкт викличе помилку try { Record.update(marcus, { name: 'FAIL' }); } catch (err) { From 7a9696925c79bd8ae307a3eecc52a9e787079b2f Mon Sep 17 00:00:00 2001 From: "@Oleh_Ottisko" <144347689+OlehOttisko@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:17:37 +0400 Subject: [PATCH 7/8] Update 3-branch.js --- JavaScript/3-branch.js | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/JavaScript/3-branch.js b/JavaScript/3-branch.js index 7f1a239..6ba6dcc 100644 --- a/JavaScript/3-branch.js +++ b/JavaScript/3-branch.js @@ -1,23 +1,18 @@ 'use strict'; class Record { - static immutable(fields) { return Record.#build(fields, false); } - static mutable(fields) { return Record.#build(fields, true); } - static #build(fields, isMutable) { const fieldSet = new Set(fields); - class Struct { static fields = Object.freeze(fields.slice()); static mutable = isMutable; - // Приймаємо об'єкт з іменованими полями замість позиційних аргументів. static create(props) { @@ -34,7 +29,6 @@ class Record { throw new Error(`Unexpected field: ${key}`); } } - return isMutable ? Object.seal(obj) : Object.freeze(obj); } } @@ -42,6 +36,7 @@ class Record { } // Оновлює мутабельний екземпляр. Залишається без змін, логіка коректна. + static update(instance, updates) { if (Object.isFrozen(instance)) { throw new Error('Cannot mutate an immutable Record instance'); @@ -72,7 +67,6 @@ module.exports = { Record }; // Приклад використання оптимізованого коду const User = Record.immutable(['id', 'name', 'email']); - const user1 = User.create({ id: 1, name: 'Marcus', From 71f97d4e82cab1c3837e48f314dbc987a7afbb2b Mon Sep 17 00:00:00 2001 From: "@Oleh_Ottisko" <144347689+OlehOttisko@users.noreply.github.com> Date: Thu, 12 Jun 2025 17:18:15 +0400 Subject: [PATCH 8/8] Update 4-default.js --- JavaScript/4-default.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/JavaScript/4-default.js b/JavaScript/4-default.js index cef4e9e..d44d792 100644 --- a/JavaScript/4-default.js +++ b/JavaScript/4-default.js @@ -24,16 +24,12 @@ function validateTypes(props, defaults) { class Record { - - static immutable(defaults) { return Record.#build(defaults, false); } - static mutable(defaults) { return Record.#build(defaults, true); } - static #build(defaults, isMutable) { // Заморожуємо структуру "класу", щоб уникнути її випадкової зміни const fields = Object.keys(defaults);