mcpField
W
web-utilities-native-js

web-utilities-native-js

github.com/agents-incDevelopment2644 installsUpdated 2d ago
Repository

About web-utilities-native-js

A practical-pattern skill for JavaScript and TypeScript developers, showing native API alternatives to common utility-library calls and noting cloning and immutable-update caveats.

Key features

  1. 01Native alternatives to utility libraries
  2. 02Deep copying with structuredClone
  3. 03Immutable array updates
  4. 04Array grouping and Set operations
  5. 05Object transforms and function utility patterns

Use cases

  1. 01Reduce utility-library dependencies
  2. 02Copy data containing dates or collections safely
  3. 03Update shared arrays immutably
  4. 04Group data by key and apply set operations

Install

Copy the command and run it in a terminal.

$npx skills add https://github.com/agents-inc/skills --skill web-utilities-native-js

After installing, type/web-utilities-native-jsin Claude Code to confirm it loaded.

Bundled files

References and scripts installed along with SKILL.md.

metadata.yaml
reference.md
examples/
arrays.md
core.md
objects.md

Details

From the project README

Native JavaScript Utility Patterns

Quick Guide: Most utility-library calls now have a native equivalent with no bundle cost. The four that change how code is written are structuredClone for deep copies, the ES2023 immutable array methods (toSorted, toReversed, toSpliced, with), Object.groupBy / Map.groupBy, and the ES2025 Set operations. The two facts that catch people out: a structured clone drops prototypes and throws on functions, and the immutable array methods copy one level deep.

Detailed Resources:

  • examples/core.md — cloning, immutable updates, negative indexing, searching from the end
  • examples/arrays.md — deduplication, set operations, grouping, chunking, sorting, compacting
  • examples/objects.md — typed pick/omit/mapValues, deep merge, lookup maps, freezing
  • reference.md — lodash-to-native table, function utilities (debounce, throttle, memoize), availability

<critical_requirements>

Before writing utility code

Reach for the native method before adding a dependency. The lodash-to-native table in reference.md settles most of it in one lookup, and the native call ships nothing.

Deep-copy with structuredClone. A JSON round trip turns a Date into a string and a Map or Set into {}, and it does so silently — the failure surfaces later, at the first .getTime().

Use the to* array methods where the array is shared. sort, reverse and splice rewrite the array in place, so a function that "returns a sorted copy" with sort has changed its caller's data.

Convert to a Set before repeated membership checks. includes inside a filter is O(n²), and the difference shows up at a few thousand elements.

</critical_requirements>


Auto-detection: structuredClone, Object.groupBy, Map.groupBy, toSorted, toReversed, toSpliced, Array.prototype.with, findLast, findLastIndex, at(-1), Object.hasOwn, Object.fromEntries, Set.prototype.union, Set.prototype.intersection, Set.prototype.difference, symmetricDifference, isSubsetOf, isDisjointFrom, lodash alternative, native JavaScript utilities

Applies to:

  • Array work — deduplicating, grouping, chunking, flattening, sorting immutably, searching from the end
  • Object work — picking, omitting, merging, key/value transformation, lookup maps
  • Set algebra — union, intersection, difference, subset tests
  • Deep copying without a dependency
  • Hand-rolled function utilities — debounce, throttle, memoize, once

Handled elsewhere:

  • Date arithmetic, parsing and formatting — Date alone does not cover it
  • Reactive updates — these methods produce new references, and what consumes a new reference is the concern of whatever owns the state
  • Schema validation and runtime parsing — these utilities reshape data that is already trusted

<decision_framework>

Native or a dependency

Three cases, in order:

  • A native method exists — use it. The table in reference.md is the lookup.
  • No native method, but the implementation is ten lines — write it. chunk, pick, omit, compact, deepMerge, debounce, throttle, memoize and once are all in this skill, written out and typed.
  • The edge cases are the feature — take the dependency. A debounce with cancel, flush and leading/trailing edges; a deep merge with per-key array strategies; lazy evaluation over a dataset large enough that intermediate arrays matter. Reimplementing these badly costs more than the bytes.

</decision_framework>


Core patterns

Pattern 1: structuredClone for deep copies

Preserves Date, Map, Set, RegExp, typed arrays and circular references — everything a JSON round trip destroys.

const snapshot = structuredClone(state);
// Preserved: Date, Map, Set, RegExp, ArrayBuffer, circular refs
// Throws on: functions, DOM nodes, Proxy, symbols
// Silently lost: the prototype — a class instance clones to a plain object

Full code: examples/core.md


Pattern 2: Immutable array methods (ES2023)

Four to* methods return a new array where the classic method rewrote the old one.

items.toSorted((a, b) => a.price - b.price); // vs .sort()
items.toReversed(); // vs .reverse()
items.toSpliced(index, 1); // vs .splice()
items.with(index, { ...items[index], done: true }); // vs items[i] = x

The copy is shallow: the new array holds the same element objects, which is why the update above spreads before writing.

Full code: examples/core.md


Pattern 3: Object.groupBy and Map.groupBy (ES2024)
const byStatus = Object.groupBy(orders, (order) => order.status);
// { pending: [...], shipped: [...] } — null-prototype, values are `T[] | undefined`

const byCustomer = Map.groupBy(orders, (order) => order.customer);
// Map keyed by the object itself — use this when the key is not a string

Object.groupBy coerces the key to a string and returns a null-prototype object, so reach for Object.hasOwn(byStatus, key) rather than byStatus.hasOwnProperty(key).

Full code: examples/arrays.md


Pattern 4: Set operations (ES2025)
setA.union(setB);
setA.intersection(setB);
setA.difference(setB); // in A, not in B
setA.symmetricDifference(setB);
setA.isSubsetOf(setB);
setA.isSupersetOf(setB);
setA.isDisjointFrom(setB);

Each returns a Set, so spread when an array is wanted. The argument is any set-like with size, has and keys — a Map qualifies, a plain array does not.

Full code: examples/arrays.md


Pattern 5: Searching and indexing from the end
logs.findLast((log) => log.level === "error"); // ES2023, single pass
logs.findLastIndex((log) => log.level === "error");
items.at(-1); // ES2022, undefined when empty
"file.backup.txt".split(".").at(-1); // "txt" — strings too

Full code: examples/core.md


Pattern 6: Object.fromEntries as the transformation pivot

entries → map/filter → fromEntries covers pick, omit, invert, mapValues, mapKeys and array-to-lookup in one shape, so those helpers are four lines each rather than a dependency.

const keyed = Object.fromEntries(items.map((item) => [item.id, item]));
const withTax = Object.fromEntries(
  Object.entries(prices).map(([k, v]) => [k, v * 1.1]),
);
const passing = Object.fromEntries(
  Object.entries(scores).filter(([, score]) => score >= 90),
);

Full code: examples/objects.md


<red_flags>

Red flags

Breaks at runtime:

  • structuredClone on anything holding a function, a DOM node or a Proxy throws DataCloneError — clone the data separately from the behaviour.
  • JSON.parse(JSON.stringify(x)) does not throw; it corrupts. A Date becomes a string, a Map or Set becomes {}, undefined members vanish, and the first method call on the result is where it surfaces.
  • Object.groupBy returns a null-prototype object, so result.hasOwnProperty(...) throws. Use Object.hasOwn.
  • Set methods reject a plain array — they need something with size, has and keys.

Surprising behaviour:

  • structuredClone discards the prototype. A class instance clones to a plain object with the same fields and none of the methods.
  • toSorted() with no comparator sorts by string, so [10, 9, 1].toSorted() is [1, 10, 9] — exactly as sort() always did.
  • The to* methods copy one level. Editing an element of the new array edits the same object in the old one.
  • arr.at(-1) types as T | undefined whatever the compiler options, so the empty case has to be handled. arr[arr.length - 1] types as T unless noUncheckedIndexedAccess is on — that is the form that lets an undefined flow on unchecked.
  • structuredClone on an Error succeeds and keeps the standard fields, but a code, a status or anything else attached to it is not in the copy.
  • filter(Boolean) drops 0, "" and false along with the nullish values, and TypeScript does not narrow the result — filter((x): x is T => x != null) does both jobs correctly.
  • Object.freeze is shallow, and outside strict mode a write to a frozen object fails silently rather than throwing.
  • Object.keys returns integer-like keys first in ascending numeric order, whatever the insertion order — only string keys keep insertion order.

</red_flags>

Comments

Markdown supported