metautil-data-structures
Skill61 starsChanged 8 months agoMITView on GitHub
Operate metautil package ADTs — ConsList, Struct, Queue, Stack, Deque, List, Trie, UnrolledList, CircularBuffer. Use when importing or calling these classes from metautil. For native Map/Object/Set/Array, use js-data-structures. For custom data structures, use data-structures.
In this repository6 other files it gives its agents.
- data-structuresSkill
- error-handlingSkill
- js-conventionsSkill
- js-data-structuresSkill
- js-gofSkill
- npm-publishSkill
The file
We split this skill into the rules it gives an agent
4 rules. About 0 are common, 0 are distinctive, and 4 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈0
Distinctive · estimate
This author's own choices. Worth a look.
4
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 8
Import from the package root.
- Very specific: line 199
Default value is true when omitted.
- Very specific: line 202
Use has to distinguish missing from stored undefined
- Very specific: line 209
Prefer structure APIs over inventing Array adapters.
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Metautil Data Structures
Import from the package root. Prefer these ADTs over ad-hoc Array/Object when the discipline (FIFO, LIFO, persistence, schema) matters.
const metautil = require('metautil');
const { ConsList, cons, uncons, Struct, Deque, Queue, Stack } = metautil;
const { List, ListNode, CircularBuffer, UnrolledList, Trie } = metautil;Choose the right structure
- FIFO jobs / BFS / sliding failure window:
Queue - LIFO undo / nested layers / brackets:
Stack - Both ends O(1), capped window:
DequeorCircularBuffer - Index + mid-sequence edit/move/group:
List - Immutable history / branching / sharing:
ConsList - Typed sealed/frozen records:
Struct - Prefix autocomplete / string keys:
Trie - Extreme enqueue/dequeue volume only:
UnrolledList(no peek / interop) - Random access on a ring:
CircularBuffer.at(notDeque)
Complexity and surface
CircularBuffer— ends O(1), index O(1) viaatDeque/Queue/Stack— ends O(1); no index (atonly on buffer)UnrolledList— ends O(1); no peek / interopList— ends O(1), index O(n)ConsList—prependO(1), walk O(n)Trie— string-key prefix map
Who has what (beyond size / isEmpty where present):
fromArray/toArray/ iterator: all exceptUnrolledList(andTrie)peek:Queue,Stackat:CircularBufferonlyevery/reduce:CircularBuffer,Deque,Stack(notQueue)clear/includes: ring family +List+Trie.clear; notConsList/UnrolledList
Interoperability
Convert through Array:
const list = List.fromArray([1, 2, 3, 4, 5]);
const queue = Queue.fromArray(list.filter((n) => n % 2 === 0).toArray());
const consList = ConsList.fromArray(queue.toArray());Deque, Queue, and Stack are thin facades over CircularBuffer (same ring storage).
Struct
Schema is inferred from default literals. Immutable uses Object.freeze; mutable uses Object.seal. Class name becomes constructor.name. Statics: fields, schema, mutable.
undefined: schemaunknown, accepts anythingnull: schemaref, accepts null, object, function[]/{}: schemaarray/object, accepts arrays / plain objects (fresh copy per instance)- primitive: schema
typeof, accepts that primitive
null (ref) ≠ {} (object); arrays fail object and plain objects fail array.
const Money = Struct.immutable('Money', { currency: 'USD', amountCents: 0 });
const a = Money.create({ amountCents: 500 });
const b = a.fork({ amountCents: 700 }); // new frozen record
// a.update(...) throws; use fork or branch
const Reading = Struct.mutable('Reading', {
deviceId: '',
millicelsius: 0,
processed: false,
});
const r = new Reading({ deviceId: 'sensor-7' });
r.update({ processed: true }); // in-place, mutable onlyfork(updates)— full shallow copy viatoObject()+ updates (independent instance)branch(updates)—Object.create(this)overlay; cheap sharing of unset fields- On a mutable branch,
updateonly works for fields the branch itself defined - Unknown field or wrong type throws
TypeError
ConsList
Immutable singly-linked cons cells with structural sharing. prepend is O(1); never mutate. ConsList.empty is a singleton (===).
const shared = ConsList.of(3, 4, 5);
const branch1 = shared.prepend(2).prepend(1); // [1, 2, 3, 4, 5]
const branch2 = shared.prepend(99); // [99, 3, 4, 5]
// branch1.tail.tail === shared; branch2.tail === shared
const { value, tail } = branch1.uncons(); // same as uncons(branch1)
const rebuilt = cons(value, tail); // same as tail.prepend(value)
let history = ConsList.of('draft v1');
history = history.prepend('draft v2');
const undone = history.tail; // O(1) restore; suffix still sharedmerge(...lists)— join in argument order; shares the last list as suffix; O(n) over all but the lastmember(value)— first shared suffix whose head===value, orempty(not a boolean)equals/includes— element-wise===- Also:
of/fromArray/fromIterable,map/filter/find/some/every,reverse,toArray - Empty
reducewithout seed throwsTypeError; emptyeveryistrue
CircularBuffer / Deque / Queue / Stack
Growable power-of-two ring (initial capacity 16, doubles on full). End ops O(1); CircularBuffer.at(i) is O(1) (-1 = last). clear() resets capacity to initial.
const WINDOW = 5;
const trace = new Deque();
trace.push(point);
if (trace.size > WINDOW) trace.shift();
const q = Queue.fromArray(jobs);
q.enqueue(job);
const next = q.dequeue(); // undefined if empty
q.peek();
const undo = new Stack();
undo.push(command);
const last = undo.pop();
undo.peek();CircularBuffer/Deque:unshift/push,shift/pop; index only on buffer viaatQueue:enqueue/dequeue/peek(front)Stack:push/pop/peek(back)- Empty
everyistrue; emptyreducewithout seed throwsTypeError
List / ListNode
Mutable doubly-linked sequence. Ends O(1); index ops O(n). Public API is value/index based. Non-integer indexes are no-ops; negatives count from the end.
const route = List.of(a, b, c);
route.insert(1, rush);
route.move(from, to);
route.rotate(1); // default 1; positive = left
const { before, after } = route.splitAt(2);
const byZone = route.groupBy((stop) => stop.zone);
const removed = route.remove(x, y); // count removed
route.sum((x) => x.cents);Mutating vs copying:
- Mutate:
append/prepend/insert/delete/drop/rotate/swap/move/reverse/sort/replace/clear - Copy:
take/slice/splitAt/map/flatMap/filter/toReversed/toSorted/clone/merge take/slicereturnnullfor empty range,take(0), or bad bounds;dropmutates in placeinsertclamps out-of-range indexes;delete/swap/moveout-of-range are no-opsreduce(fn, initial)— initial is required (unlike ConsList / CircularBuffer)avgon empty is0;min/maxon empty areundefined- Also:
at/set,indexOf/lastIndexOf,includes,some/every/find/findIndex
ListNode is low-level (create / append / prepend / unlink / seek / fromArray / copy / link) for custom structures that own head/tail/size. seek non-integer returns null. Do not mutate prev/next of nodes owned by a List.
UnrolledList
High-throughput FIFO: pooled fixed-size array nodes. Use when volume is high and you only need enqueue/dequeue.
const pending = new UnrolledList({ nodeSize: 256, poolSize: 4 });
pending.enqueue(task);
let task = pending.dequeue();
while (task !== undefined) {
task();
task = pending.dequeue();
}- Defaults:
nodeSize: 1024,poolSize: 2 - Raise
nodeSizefor larger bursts; raisepoolSizeif drain/refill churns nodes - No
peek,fromArray, iterator, orincludes enqueue(undefined)is a real item — emptydequeue()is indistinguishable from that value
Trie
Prefix tree for string keys.
const trie = new Trie();
trie.insert('cat');
trie.insert('car', 42);
trie.insert('gone', undefined);
trie.has('gone'); // true — present; get returns undefined
trie.get('car'); // 42
trie.complete('ca'); // order may vary
trie.delete('car'); // prunes empty branches- Default value is
truewhen omitted; re-insert same key does not bumpsize - Empty string
''is a valid key insertnon-string throwsTypeError;has/get/deletenon-string soft-fail (false/undefined/false)- Use
hasto distinguish missing from storedundefined
Contracts
- Empty
pop/dequeue/shiftreturnundefined - Empty
reducewithout seed throwsTypeError(ConsList / CircularBuffer family); List always needs a seed - Equality /
includes/memberuse strict===(NaNdoes not match) - Prefer structure APIs over inventing Array adapters; convert via
toArray/fromArraywhen crossing types
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

