Basic Usage
Start at package root for common utilities. Move to a category entry point for the broader typed toolkit. Arsenal keeps helpers that improve readability, narrowing, immutable updates, or edge-case consistency; exact platform aliases stay out.
import { groupBy, retry } from '@vielzeug/arsenal';
const users = [
{ id: 'a1', role: 'admin' },
{ id: 'u1', role: 'user' },
{ id: 'u2', role: 'user' },
];
const byRole = groupBy(users, (user) => user.role);
const health = await retry(() => fetch('/health').then((response) => response.json()));
console.log(byRole, health);Use category imports for APIs absent from root:
import { fuzzyFilter } from '@vielzeug/arsenal/array';
import { taskPool } from '@vielzeug/arsenal/async';
import { cache } from '@vielzeug/arsenal/cache';
import { tryParseJson } from '@vielzeug/arsenal/object';Transform Collections
Use /array for transforms that preserve input immutability. filterMap combines mapping and omission; indexBy and groupBy build lookup structures without mutation.
import { filterMap, indexBy, sort } from '@vielzeug/arsenal/array';
const products = [
{ id: 'p1', price: 20, published: true },
{ id: 'p2', price: 10, published: false },
{ id: 'p3', price: 15, published: true },
];
const publishedLabels = filterMap(products, (product) => (product.published ? `${product.id}: ${product.price}` : undefined));
const byId = indexBy(products, (product) => product.id);
const byPrice = sort(products, (product) => product.price);
console.log(publishedLabels, byId, byPrice);Search Explicit Fields
Search string arrays directly. Object collections require select, so callers define exactly what can match.
import { fuzzyFilter, fuzzyScore } from '@vielzeug/arsenal/array';
const users = [
{ email: 'alice@example.com', name: 'Alice' },
{ email: 'bob@example.com', name: 'Bob' },
];
const matches = fuzzyFilter(users, 'alice', { select: (user) => [user.name, user.email] });
const ranked = fuzzyScore(users, 'ali', { select: (user) => user.name });Work with Object Data
Use /object for paths, key selection, stable cache keys, and object transforms.
import { getPathOr, hash, omit, pick } from '@vielzeug/arsenal/object';
const config = { api: { host: 'localhost', port: 3000 }, debug: true };
const port = getPathOr(config, 'api.port', 8080);
const publicConfig = pick(config, ['api']);
const productionConfig = omit(config, ['debug']);
const key = hash({ port, productionConfig });
console.log(publicConfig, key);Parse and Validate JSON
tryParseJson distinguishes syntax failure from schema failure. Treat successful values as unknown, then validate with Spell or application code.
import { tryParseJson } from '@vielzeug/arsenal/object';
import { s } from '@vielzeug/spell';
const User = s.object({ id: s.string(), name: s.string() });
const parsed = tryParseJson(raw);
if (!parsed.ok) throw parsed.error;
const user = User.parse(parsed.value);Bound Concurrent Work
Use parallel for one finite collection. Use taskPool when tasks arrive over time or need disposal.
import { parallel, taskPool } from '@vielzeug/arsenal/async';
const metadata = await parallel(urls, (url) => fetch(url).then((response) => response.json()), { limit: 4 });
const pool = taskPool({ concurrency: 2 });
const profile = await pool.run((signal) => fetch('/profile', { signal }).then((response) => response.json()));
await pool.idle();
pool.dispose();
console.log(metadata, profile);Cache Loaded Values
Use cache for process-local values. Keys retain native Map identity. getOrLoad deduplicates concurrent loads for one key.
import { cache } from '@vielzeug/arsenal/cache';
type Profile = { id: string; name: string };
const profiles = cache<string, Profile>({ capacity: 100, ttlMs: 60_000 });
const profile = await profiles.getOrLoad('me', () => fetch('/profile').then((response) => response.json()));
console.log(profiles.entries());
profiles.delete('me');
const freshProfile = await profiles.getOrLoad('me', () => fetch('/profile').then((response) => response.json()));
console.log(profile, freshProfile);Test Deterministic Randomness
Random helpers use cryptographic entropy by default. Pass RandomSource in tests when output must be deterministic.
import { random, randomFloat, type RandomSource } from '@vielzeug/arsenal/random';
const source: RandomSource = { next: () => 0.5 };
randomFloat(source); // 0.5
random(1, 4, source); // 3Encode Text for URLs and QR Codes
Share codes ride in URLs and QR payloads, where +, /, and padding = break consumers. The base64url codecs are environment-independent — no btoa, no Buffer — so the same encode/decode pair works in the browser, in workers, and in Node.
import { base64UrlToText, textToBase64Url } from '@vielzeug/arsenal';
const code = textToBase64Url(JSON.stringify({ hunterId: 'daeron', version: 2 }));
// eyJodW50ZXJJZCI6ImRhZXJvbiIsInZlcnNpb24iOjJ9 — safe in a query string or QR
const payload = JSON.parse(base64UrlToText(code));Decoding throws on characters outside the base64url alphabet, so corrupted input fails loudly instead of decoding to garbage.
Working with Other Vielzeug Libraries
Use Spell after tryParseJson for typed external data. Use Vault instead of cache when data must survive reloads or process restart.
import { tryParseJson } from '@vielzeug/arsenal/object';
import { s } from '@vielzeug/spell';
const Settings = s.object({ theme: s.string() });
const parsed = tryParseJson(rawSettings);
const settings = parsed.ok ? Settings.parse(parsed.value) : { theme: 'system' };Best Practices
- Import common utilities from package root.
- Import the broader toolkit from category entry points.
- Prefer Arsenal when a helper improves typing, intent, immutable updates, or edge-case behavior.
- Use the platform directly for exact aliases such as
crypto.randomUUID()andArray.prototype.flat(). - Pass
selectfor every fuzzy search over objects. - Validate parsed JSON before using it as application data.
- Use
parallelfor finite batches andtaskPoolfor ongoing work. - Dispose task pools when their owner ends.
- Use
cacheonly for in-memory data. - Inject
RandomSourcein deterministic tests.