Loading...
Loading...
Apply Gang of Four and related design patterns in JavaScript and TypeScript. Use when implementing creational, structural, or behavioral patterns, or when the user mentions factories, builder, prototype, flyweight, singleton, object pool, adapter, wrapper, decorator, proxy, bridge, composite, facade, chain of responsibility, command, interpreter, iterator, mediator, memento, EventEmitter, EventTarget, state, strategy, template method, visitor, revealing constructor, actor, service locator, or any other pattern.
npx skill4agent add metarhia/metaskills js-gofconst dataAccess = {
fs: {
createDatabase: (...args) => new FileStorage(...args),
createCursor: (...args) => new FileLineCursor(...args),
},
minio: {
// factories collection for minio
},
// other implementations
};
// Usage
const accessLayer = dataAccess.fs;
const storage = accessLayer.createDatabase('./storage.dat');
const cursor = accessLayer.createCursor({ city: 'Roma' }, storage);
for await (const record of cursor) {
console.dir(record);
}class QueryBuilder {
#options;
constructor(table) {
this.#options = { table, where: {}, limit: null, order: null };
}
where(cond) {
Object.assign(this.#options.where, cond);
return this;
}
order(field) {
this.#options.order = field;
return this;
}
limit(n) {
this.#options.limit = n;
return this;
}
build() {
return { ...this.#options };
}
}const query = new QueryBuilder('User')
.where({ active: true })
.order('name')
.limit(10)
.build();await new QueryBuilderconst query = await new QueryBuilder({
entity: 'User',
where: { active: true },
order: 'name',
limit: 10,
});setPrototypeOfconst createUser = (name, role) => ({ name, role, createdAt: Date.now() });
const createAdmin = (name) => createUser(name, 'admin');class Connection {
constructor(url) {
// implementation
}
// implementation
}
const factory = (() => {
let index = 0;
return () => new Connection(`http://10.0.0.1/${index++}`);
})();ifswitchclass Person {
constructor(name) {
this.name = name;
}
static factory(name) {
return new Person(name);
}
}class Product {
constructor(value) {
this.field = value;
}
}
class Creator {
factoryMethod(...args) {
return new Product(...args);
}
}const proto = { type: 'widget', color: 'blue' };
const clone = () => ({ ...proto });class Point {
#x;
#y;
constructor(x, y) {
this.#x = x;
this.#y = y;
}
move(x, y) {
this.#x += x;
this.#y += y;
}
clone() {
return new Point(this.#x, this.#y);
}
}
class Line {
#start;
#end;
constructor(start, end) {
this.#start = start;
this.#end = end;
}
move(x, y) {
this.#start.move(x, y);
this.#end.move(x, y);
}
clone() {
const start = this.#start.clone();
const end = this.#end.clone();
return new Line(start, end);
}
}
// Usage
const p1 = new Point(0, 0);
const p2 = new Point(10, 20);
const line = new Line(p1, p2);
const cloned = line.clone();
cloned.move(2, 3);const point = (x, y) => {
const move = (dx, dy) => {
x += dx;
y += dy;
};
const clone = () => point(x, y);
return { move, clone };
};
// Usage
const { move, clone } = point(10, 20);
const c1 = clone();
move(-5, 10);const flyweightPool = new Map();
const getFlyweight = (shared) => {
const { char, font, size } = shared;
const key = `${char}:${font}:${size}`;
let flyweight = flyweightPool.get(key);
if (!flyweight) {
flyweight = Object.freeze({ ...shared });
flyweightPool.set(key, flyweight);
}
return flyweight;
};
const createChar = (char, font, size, row, col) => {
const intrinsic = getFlyweight({ char, font, size });
return { intrinsic, row, col };
};
const a1 = createChar('A', 'Arial', 12, 0, 0);
const a2 = createChar('A', 'Arial', 12, 0, 5);
console.log(a1.intrinsic === a2.intrinsic); // trueconst connections = new Map(); // shared state
const nop = () => {}; // pure function with no state
module.exports = { connections, nop };class Singleton {
static #instance;
constructor() {
const instance = Singleton.#instance;
if (instance) return instance;
Singleton.#instance = this;
}
}function Singleton() {
const { instance } = Singleton;
if (instance) return instance;
Singleton.instance = this;
}const Singleton = new (function () {
const single = this;
return function () {
return single;
};
})();const Singleton = (() => {
let instance;
class Singleton {
constructor() {
if (instance) return instance;
instance = this;
}
}
return Singleton;
})();const singleton = (() => {
const instance = {};
return () => instance;
})();const singleton = (
(instance) => () =>
instance
)({});class Pool {
#available = [];
#factory = null;
constructor(factory, size) {
this.#factory = factory;
for (let i = 0; i < size; i++) this.#available.push(factory());
}
acquire() {
return this.#available.pop() || this.#factory();
}
release(obj) {
this.#available.push(obj);
}
}const promisify =
(fn) =>
(...args) =>
new Promise((resolve, reject) => {
fn(...args, (err, data) => (err ? reject(err) : resolve(data)));
});const timer = new Timer(1000); // wraps setInterval
for await (const step of timer) {
// gives [Symbol.asyncIterator] contract
console.log({ step });
}class ArrayToQueueAdapter {
#array = null;
constructor(array) {
if (!Array.isArray(array)) {
throw new Error('Array instance expected');
}
this.#array = array;
}
enqueue(data) {
this.#array.push(data);
}
dequeue() {
return this.#array.pop();
}
get count() {
return this.#array.length;
}
}const withLogging =
(fn, label) =>
(...args) => {
console.log(`${label} called`, args);
const result = fn(...args);
console.log(`${label} returned`, result);
return result;
};const wrapInterface = (anInterface) => {
const wrapped = {};
for (const key in anInterface) {
const fn = anInterface[key];
wrapped[key] = wrapFunction(fn);
}
return wrapped;
};class Timeout {
constructor(fn, msec) {
this.function = fn;
this.timer = setTimeout(() => {
this.timer = null;
}, msec);
}
execute(...args) {
let result = undefined;
if (!this.timer) return result;
clearTimeout(this.timer);
this.timer = null;
result = this.function(...args);
return result;
}
}StringAddressStringclass AddressString {
#value;
constructor(value) {
if (typeof value !== 'string') {
throw new TypeError('Address must be a string');
}
const str = value.trim();
if (str === '') throw new TypeError('Address must be a non-empty');
this.#value = str;
}
get city() {
const index = this.#value.indexOf(',');
return this.#value.slice(0, index).trim();
}
toString() {
return this.#value;
}
valueOf() {
return this.#value;
}
}
// Usage
const address = new AddressString('London, 221B Baker Street');
console.log(address.city); // 'London'
console.log(`Delivery to: ${address}`);const KMH_TO_MPH = 0.621371;
class SpeedValue {
#kmh;
constructor(value, unit = 'kmh') {
if (typeof value !== 'number') {
throw new TypeError('Speed must be a number');
}
if (value < 0) throw new TypeError('Speed must be non-negative');
if (unit !== 'kmh' && unit !== 'mph') {
throw new TypeError('Unit must be kmh or mph');
}
this.#kmh = unit === 'mph' ? value / KMH_TO_MPH : value;
}
get kmh() {
return this.#kmh;
}
get mph() {
return this.#kmh * KMH_TO_MPH;
}
toString() {
return `${this.#kmh} km/h`;
}
valueOf() {
return this.#kmh;
}
}const add = (a, b) => a + b;
const loggedAdd = withLogging(add, 'add');const validator = new MinLengthValidator(
new EmailValidator(new RequiredValidator(new BaseValidator())),
20,
);
const input = 'timur.shemsedinov@gmail.com';
const result = validator.validate(input);const VALIDATOR_META = Symbol('validatorMeta');
class EmailValidator {
constructor(next) {
this.next = next;
this[VALIDATOR_META] = { type: 'email' };
}
}/**
* @typedef {{ type: string, label: string }} ValidatorMeta
* @typedef {{ validate(v: string): boolean, meta: ValidatorMeta }} Validator
*/
/** @type {Validator} */
const validator = Object.assign(
new EmailValidator(new RequiredValidator(new BaseValidator())),
{ meta: { type: 'email', label: 'Email field' } },
);const metadata = new WeakMap();
const createValidator = ({ args, meta }) => {
const instance = new Validator(...args);
metadata.set(instance, meta);
return instance;
};
module.exports = { createValidator };Proxyconst fs = require('node:fs');
const statistics = { bytes: 0, chunks: 0, events: {} };
class StatReadStream extends fs.ReadStream {
emit(name, data) {
if (name === 'data') {
statistics.bytes += data.length;
statistics.chunks++;
}
const counter = statistics.events[name] || 0;
statistics.events[name] = counter + 1;
super.emit(name, data);
}
}
const getStatistics = () => structuredClone(statistics);
const createReadStream = (path, options) => new StatReadStream(path, options);
module.exports = { ...fs, createReadStream, getStatistics };class CommunicationProtocol {
sendCommand(device, command) {
console.log({ device, command });
throw new Error('sendCommand() must be implemented');
}
}
class MQTTProtocol extends CommunicationProtocol {
sendCommand(device, command) {
console.log(`[MQTT] Sending '${command}' to ${device}`);
}
}
class HTTPProtocol extends CommunicationProtocol {
sendCommand(device, command) {
console.log(`[HTTP] Sending '${command}' to ${device}`);
}
}
class IoTDevice {
constructor(name, protocol) {
this.name = name;
this.protocol = protocol;
}
operate(command) {
this.protocol.sendCommand(this.name, command);
}
}
class SmartLight extends IoTDevice {
turnOn() {
this.operate('Turn On Light');
}
turnOff() {
this.operate('Turn Off Light');
}
}
class SmartThermostat extends IoTDevice {
setTemperature(temp) {
this.operate(`Set Temperature to ${temp}°C`);
}
}const calculateTotal = (order) => {
const items = Array.isArray(order) ? order : Object.values(order);
return items.reduce((sum, item) => {
if (typeof item.price === 'number') return sum + item.price;
else return sum + calculateTotal(item);
}, 0);
};const json = `{
"electronics": {
"laptop": { "price": 1200 },
"accessories": {
"mouse": { "price": 25 },
"keyboard": { "price": 75 }
}
},
"books": {
"fiction": { "price": 15 },
"technical": { "price": 60 }
}
}`;
const order = JSON.parse(json);
console.log(calculateTotal(order));const createApi = (db, cache, logger) => ({
async getUser(id) {
const cached = cache.get(id);
if (cached) return cached;
const user = await db.row('User', ['*'], { id });
cache.set(id, user);
logger.info(`User ${id} loaded`);
return user;
},
});scheduler.task('name2', '2019-03-12T14:37Z', (done) => {
setTimeout(() => {
done(new Error('task failed'));
}, 1100);
});class AccountService {
constructor(context) {
this.context = context;
}
getBalance(accountId) {
const { console, accessPolicy, user } = this.context;
console.log(`User ${user.name} requesting balance for ${accountId}`);
if (!accessPolicy.check(user.role, 'read:balance')) {
console.error('Access denied: insufficient permissions');
return null;
}
const balance = 15420.5;
console.log('Access granted');
return balance;
}
}
class AccessPolicy {
constructor() {
this.permissions = {
admin: ['read:balance', 'read:transactions', 'write:transactions'],
user: ['read:balance'],
guest: [],
};
}
check(role, permission) {
return this.permissions[role]?.includes(permission);
}
}
class User {
constructor(name, role) {
this.name = name;
this.role = role;
}
}
const accessPolicy = new AccessPolicy();
const user = new User('Marcus', 'admin');
const context = { console, accessPolicy, user };
const accountService = new AccountService(context);
const balance = accountService.getBalance('Account-123');
console.log(`Balance = $${balance}`);const createAccountService = (context) => {
const { console, accessPolicy, user } = context;
const getBalance = (accountId) => {
console.log(`User ${user.name} requesting balance for ${accountId}`);
if (!accessPolicy.check(user.role, 'read:balance')) {
console.error('Access denied: insufficient permissions');
return null;
}
return 15420.5;
};
const getTransactions = (accountId) => {
if (!accessPolicy.check(user.role, 'read:transactions')) {
console.error('Access denied: insufficient permissions');
return null;
}
console.log(`User ${user.name} reading transactions for ${accountId}`);
return ['TR-123', 'TR-456', 'TR-789'];
};
return { getBalance, getTransactions };
};
const accessPolicy = {
permissions: {
admin: ['read:balance', 'read:transactions', 'write:transactions'],
user: ['read:balance'],
guest: [],
},
check: (role, permission) =>
accessPolicy.permissions[role]?.includes(permission),
};
const context = {
console,
accessPolicy,
user: { name: 'Marcus', role: 'admin' },
};
const accountService = createAccountService(context);
console.log('Balance:', accountService.getBalance('ACC-001'));
console.log('Transactions:', accountService.getTransactions('ACC-001'));