struct
bun add @stopcock/structImmutable data structures that return new instances on mutation. Old references stay valid, so they’re good for undo/redo, time-travel debugging, and concurrent reads.
import { HashMap, SortedSet } from '@stopcock/struct'
const m = HashMap.of([['a', 1], ['b', 2]])const m2 = HashMap.set(m, 'c', 3)HashMap.get(m2, 'c') // 3HashMap.get(m, 'c') // undefined, original unchangedReal-world patterns
Section titled “Real-world patterns”Immutable state updates
Section titled “Immutable state updates”import { HashMap } from '@stopcock/struct'
let state = HashMap.of([['count', 0], ['name', 'Alice']])
// Each update returns a new map. Old state is preserved.const prev = statestate = HashMap.set(state, 'count', 1)
HashMap.get(prev, 'count') // 0, unchangedHashMap.get(state, 'count') // 1Priority queue with SortedSet
Section titled “Priority queue with SortedSet”import { SortedSet } from '@stopcock/struct'
type Job = { id: string; priority: number }const byPriority = (a: Job, b: Job) => a.priority - b.priority
let queue = SortedSet.of<Job>([], byPriority)queue = SortedSet.add(queue, { id: 'a', priority: 3 })queue = SortedSet.add(queue, { id: 'b', priority: 1 })queue = SortedSet.add(queue, { id: 'c', priority: 2 })
SortedSet.min(queue) // { id: 'b', priority: 1 }Sliding window with Deque
Section titled “Sliding window with Deque”import { Deque } from '@stopcock/struct'
let window = Deque.empty<number>()for (const value of stream) { window = Deque.pushBack(window, value) if (Deque.size(window) > 100) { const [, rest] = Deque.popFront(window) window = rest }}HashMap
Section titled “HashMap”HashMap.empty<K, V>(): HashMap<K, V>HashMap.of<K, V>(entries: [K, V][]): HashMap<K, V>HashMap.get<K, V>(map: HashMap<K, V>, key: K): V | undefinedHashMap.set<K, V>(map: HashMap<K, V>, key: K, value: V): HashMap<K, V>HashMap.remove<K, V>(map: HashMap<K, V>, key: K): HashMap<K, V>HashMap.has<K, V>(map: HashMap<K, V>, key: K): booleanHashMap.size<K, V>(map: HashMap<K, V>): numberHashMap.entries<K, V>(map: HashMap<K, V>): [K, V][]HashMap.keys<K, V>(map: HashMap<K, V>): K[]HashMap.values<K, V>(map: HashMap<K, V>): V[]HashMap.map<K, V, U>(map: HashMap<K, V>, f: (v: V, k: K) => U): HashMap<K, U>HashMap.filter<K, V>(map: HashMap<K, V>, pred: (v: V, k: K) => boolean): HashMap<K, V>HashMap.merge<K, V>(a: HashMap<K, V>, b: HashMap<K, V>): HashMap<K, V>SortedSet
Section titled “SortedSet”SortedSet.empty<A>(cmp?: (a: A, b: A) => number): SortedSet<A>SortedSet.of<A>(items: A[], cmp?: (a: A, b: A) => number): SortedSet<A>SortedSet.add<A>(set: SortedSet<A>, value: A): SortedSet<A>SortedSet.remove<A>(set: SortedSet<A>, value: A): SortedSet<A>SortedSet.has<A>(set: SortedSet<A>, value: A): booleanSortedSet.size<A>(set: SortedSet<A>): numberSortedSet.min<A>(set: SortedSet<A>): A | undefinedSortedSet.max<A>(set: SortedSet<A>): A | undefinedSortedSet.toArray<A>(set: SortedSet<A>): A[]SortedSet.union<A>(a: SortedSet<A>, b: SortedSet<A>): SortedSet<A>SortedSet.intersection<A>(a: SortedSet<A>, b: SortedSet<A>): SortedSet<A>SortedSet.difference<A>(a: SortedSet<A>, b: SortedSet<A>): SortedSet<A>Double-ended queue with O(1) push/pop on both ends.
Deque.empty<A>(): Deque<A>Deque.of<A>(items: A[]): Deque<A>Deque.pushFront<A>(deque: Deque<A>, value: A): Deque<A>Deque.pushBack<A>(deque: Deque<A>, value: A): Deque<A>Deque.popFront<A>(deque: Deque<A>): [A | undefined, Deque<A>]Deque.popBack<A>(deque: Deque<A>): [A | undefined, Deque<A>]Deque.peekFront<A>(deque: Deque<A>): A | undefinedDeque.peekBack<A>(deque: Deque<A>): A | undefinedDeque.size<A>(deque: Deque<A>): numberDeque.toArray<A>(deque: Deque<A>): A[]