Eshu
view release on metacpan or search on metacpan
t/0107-realworld-js-patterns.t view on Meta::CPAN
this.#store.delete(key);
});
set(key, value) {
const ref = new WeakRef(value);
this.#store.set(key, ref);
this.#registry.register(value, key);
}
get(key) {
const ref = this.#store.get(key);
if (!ref) return undefined;
const val = ref.deref();
if (val === undefined) {
this.#store.delete(key);
}
return val;
}
has(key) {
return this.get(key) !== undefined;
}
}
END
my $exp = <<'END';
class Cache {
#store = new Map();
#registry = new FinalizationRegistry((key) => {
this.#store.delete(key);
});
set(key, value) {
const ref = new WeakRef(value);
this.#store.set(key, ref);
this.#registry.register(value, key);
}
get(key) {
const ref = this.#store.get(key);
if (!ref) return undefined;
const val = ref.deref();
if (val === undefined) {
this.#store.delete(key);
}
return val;
}
has(key) {
return this.get(key) !== undefined;
}
}
END
is(Eshu->indent_js($in), $exp, 'JS: WeakRef cache with FinalizationRegistry callback');
}
# CommonJS module pattern
{
my $in = <<'END';
'use strict';
const path = require('path');
const fs = require('fs');
const DEFAULT_OPTS = {
encoding: 'utf8',
recursive: false,
filter: null,
};
function readDir(dir, opts) {
opts = Object.assign({}, DEFAULT_OPTS, opts);
const results = [];
function walk(current) {
const entries = fs.readdirSync(current, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(current, entry.name);
if (entry.isDirectory()) {
if (opts.recursive) walk(full);
} else {
if (!opts.filter || opts.filter(entry.name)) {
results.push(full);
}
}
}
}
walk(dir);
return results;
}
module.exports = { readDir };
END
my $exp = <<'END';
'use strict';
const path = require('path');
const fs = require('fs');
const DEFAULT_OPTS = {
encoding: 'utf8',
recursive: false,
filter: null,
};
function readDir(dir, opts) {
opts = Object.assign({}, DEFAULT_OPTS, opts);
const results = [];
function walk(current) {
const entries = fs.readdirSync(current, { withFileTypes: true });
for (const entry of entries) {
const full = path.join(current, entry.name);
if (entry.isDirectory()) {
if (opts.recursive) walk(full);
} else {
if (!opts.filter || opts.filter(entry.name)) {
results.push(full);
}
}
}
}
walk(dir);
return results;
}
module.exports = { readDir };
END
is(Eshu->indent_js($in), $exp, 'JS: CommonJS module with recursive directory walk');
}
# TypeScript-style interface comment block + type guards
{
my $in = <<'END';
// Type guard helpers for runtime validation
function isString(val) {
return typeof val === 'string';
}
function isRecord(val) {
return val !== null && typeof val === 'object' && !Array.isArray(val);
}
function validateConfig(raw) {
if (!isRecord(raw)) throw new TypeError('config must be an object');
const { host, port, tls = false } = raw;
if (!isString(host) || host.length === 0) {
throw new TypeError('config.host must be a non-empty string');
}
if (typeof port !== 'number' || port < 1 || port > 65535) {
throw new RangeError(`config.port must be 1-65535, got ${port}`);
}
return { host, port: Math.floor(port), tls: Boolean(tls) };
}
END
my $exp = <<'END';
// Type guard helpers for runtime validation
function isString(val) {
return typeof val === 'string';
}
function isRecord(val) {
return val !== null && typeof val === 'object' && !Array.isArray(val);
}
function validateConfig(raw) {
if (!isRecord(raw)) throw new TypeError('config must be an object');
const { host, port, tls = false } = raw;
if (!isString(host) || host.length === 0) {
throw new TypeError('config.host must be a non-empty string');
}
if (typeof port !== 'number' || port < 1 || port > 65535) {
throw new RangeError(`config.port must be 1-65535, got ${port}`);
}
return { host, port: Math.floor(port), tls: Boolean(tls) };
}
END
is(Eshu->indent_js($in), $exp, 'JS: type guard functions with destructuring and template literals');
}
# Worker thread message handler pattern
{
my $in = <<'END';
const { parentPort, workerData } = require('worker_threads');
const handlers = {
ping(data) {
return { type: 'pong', ts: Date.now() };
},
process(data) {
const { items } = data;
const results = items.map((item) => {
try {
return { ok: true, value: transform(item) };
} catch (err) {
return { ok: false, error: err.message };
}
});
return { type: 'result', results };
},
};
parentPort.on('message', (msg) => {
const handler = handlers[msg.type];
if (!handler) {
parentPort.postMessage({ type: 'error', message: `Unknown: ${msg.type}` });
return;
}
try {
const reply = handler(msg.data);
parentPort.postMessage(reply);
} catch (err) {
parentPort.postMessage({ type: 'error', message: err.message });
}
});
END
my $exp = <<'END';
const { parentPort, workerData } = require('worker_threads');
const handlers = {
ping(data) {
return { type: 'pong', ts: Date.now() };
},
process(data) {
const { items } = data;
const results = items.map((item) => {
try {
return { ok: true, value: transform(item) };
} catch (err) {
return { ok: false, error: err.message };
}
});
return { type: 'result', results };
},
};
parentPort.on('message', (msg) => {
const handler = handlers[msg.type];
if (!handler) {
parentPort.postMessage({ type: 'error', message: `Unknown: ${msg.type}` });
return;
}
try {
const reply = handler(msg.data);
parentPort.postMessage(reply);
} catch (err) {
parentPort.postMessage({ type: 'error', message: err.message });
}
});
END
is(Eshu->indent_js($in), $exp, 'JS: worker_threads message dispatch with try/catch');
}
# Proxy / Reflect traps
{
my $in = <<'END';
function createReadOnlyProxy(target) {
return new Proxy(target, {
set(obj, prop, value) {
throw new TypeError(`Cannot set property "${String(prop)}" on read-only object`);
},
deleteProperty(obj, prop) {
throw new TypeError(`Cannot delete property "${String(prop)}" on read-only object`);
},
defineProperty(obj, prop, descriptor) {
throw new TypeError(`Cannot define property "${String(prop)}" on read-only object`);
},
get(obj, prop, receiver) {
const val = Reflect.get(obj, prop, receiver);
if (val !== null && typeof val === 'object') {
return createReadOnlyProxy(val);
}
return val;
},
});
}
END
my $exp = <<'END';
( run in 1.036 second using v1.01-cache-2.11-cpan-6aa56a78535 )