WizJS
A practical, opinionated toolbox for everyday code, with Python's itertools, functools and collections in JavaScript at its core: 18 lazy generators, cache and partial, Counter and DefaultDict — almost all under their Python names. New: BBTreeMap, an ordered map built after a Rust B-tree, with lookups up to 2.75× faster than a classic B-tree's. Every area is its own package subpath, so you can require lang/checks on its own and leave the rest behind. Zero runtime dependencies; plain CommonJS, tested on Node.
npm install @wizhut_tech/wizjsPython's itertools, Counter and defaultdict in JavaScript
1itertools → lang/itertools
The same names in the same argument order, and just as lazy: every function but
takeis a generator, so nothing runs until you read from it — withfor…of, spread orArray.from. Tuples come back as arrays.zip,rangeandenumerate, which are builtins in Python, live here too.Python
import itertools evens = itertools.count(0, 2) list(itertools.islice(evens, 4)) # [0, 2, 4, 6] list(itertools.pairwise('abc')) # [('a', 'b'), ('b', 'c')]
JavaScript, WizJS
const itertools = require( '@wizhut_tech/wizjs/lang/itertools' ) const evens = itertools.count(0, 2) Array.from(itertools.islice(evens, 4)) // → [0, 2, 4, 6] Array.from(itertools.pairwise('abc')) // → [['a', 'b'], ['b', 'c']]
2Counter → lang/collections
Built the same way, from a string, an array or any other iterable. Square brackets become
get, which answers 0 for anything unseen without adding it, andmost_commonis spelledmostCommon.totaland a lazyelementsare there too;update,subtractand Counter arithmetic are not.Python
from collections import Counter c = Counter('banana') c.most_common(2) # [('a', 3), ('n', 2)] c['z'] # 0JavaScript, WizJS
const { Counter } = require( '@wizhut_tech/wizjs/lang/collections' ) const c = new Counter('banana') c.mostCommon(2) // → [['a', 3], ['n', 2]] c.get('z') // → 0
3defaultdict → DefaultDict
Also in
lang/collections, and it takes a factory the same way —Arrayfor a list of groups.getfills a missing key from the factory, as square brackets do in Python, andpeekreads without filling, like Python'sd.get.Without a factory, a missing key gives
undefinedrather than a KeyError.Python
from collections import defaultdict groups = defaultdict(list) for w in ['ant', 'bee', 'ape']: groups[w[0]].append(w) dict(groups) # {'a': ['ant', 'ape'], 'b': ['bee']}JavaScript, WizJS
const { DefaultDict } = require( '@wizhut_tech/wizjs/lang/collections' ) const groups = new DefaultDict(Array) for (const w of ['ant', 'bee', 'ape']) groups.get(w[0]).push(w) Object.fromEntries(groups.entries()) // → { a: ['ant', 'ape'], b: ['bee'] }
4functools → lang/functools
partial,reduceandcachekeep their Python names, andcacheanswers tomemoizetoo. A decorator becomes a wrapper: pass the function tocacheand keep what comes back.composeandoncecome on top — Python's functools has neither.Python
from functools import cache @cache def fib(n): if n < 2: return n return fib(n - 1) + fib(n - 2) fib(50) # 12586269025JavaScript, WizJS
const { cache } = require( '@wizhut_tech/wizjs/lang/functools' ) const fib = cache((n) => n < 2 ? n : fib(n - 1) + fib(n - 2)) fib(50) // → 12586269025
What's there, and what isn't
Every name on the left is Python's. Where WizJS has it, the right says what it is called and whether it is lazy; where it does not, the row says so.
| Python | WizJS |
|---|---|
| count cycle repeat compress dropwhile takewhile filterfalse starmap islice pairwise batched | Same names lazyArguments in the same order |
| chain | chain lazyNo from_iterable: spread the lists instead, chain(...lists) |
| groupby | groupby lazyEach group is an array |
| zip_longest | zip_longest lazyfillvalue goes last, in an object: { fillvalue: 0 } |
| product | product lazyNo repeat: pass the iterable once per repeat |
| zip range enumerate | Same names lazyPython builtins, kept with itertools |
| list(islice(it, n)) | take(n, it) eagerReturns an array |
| accumulate | Not in WizJS — arrays.accumulate folds to a single value |
| tee permutations combinations | Not in WizJS — product is the only combinatoric iterator |
| Python | WizJS |
|---|---|
| partial reduce | Same names partial presets positional arguments only |
| cache | cache Also called memoize. Unbounded, like Python's; fn.cache.clear() empties it |
| lru_cache cmp_to_key wraps | Not in WizJS |
| Python | WizJS |
|---|---|
| Counter | Counter most_common is mostCommon. No update, subtract or arithmetic; add(item, n) adds to one count |
| defaultdict | DefaultDict d[key] is get(key), which fills a missing key; Python's d.get(key) is peek(key) |
| deque namedtuple OrderedDict ChainMap | Not in WizJS |
Where it differs from Python
Most of it behaves exactly as in Python — groupby still wants sorted input, and mostCommon breaks ties in the order items first appeared. These are the places JavaScript changes the answer.
- Tuples are arrays.
pairwise,zipandmostCommonhand back arrays, andzip_longestfills gaps withundefined, where Python uses None. - Keys follow Map rules.
1and'1'are two keys,NaNis one, and objects and arrays match by identity rather than by value — inCounter,DefaultDictandcachealike. A Counter built from a plain object has string keys. getfills a missing key. On a DefaultDict,get(key)is Python'sd[key]; for Python'sd.get(key, default), usepeek(key, default).repeat(x, 0)never stops. A count of zero or less repeats forever, where Python's yields nothing.- Stopping early closes a generator. Hand one to
take,isliceortakewhileand it is finished once they stop short; Python'sisliceleaves it where it stopped.
One library, organised by what you're trying to do
Pull in everything
The root import still hands back the whole nested object — handy at the REPL, or in scripts where one require is simpler.
const wizjs = require('@wizhut_tech/wizjs') wizjs.lang.checks.isNil(undefined) // → true wizjs.lang.checks.isObject({ a: 1 }) // → true wizjs.math.numbers.clamp(42, 0, 10) // → 10
New — import just the area you need
Every namespace area answers to its own subpath, so you load that area and the few it builds on — not the whole library. Needs Node 14.13 or newer.
const { isNil, isEmpty } = require('@wizhut_tech/wizjs/lang/checks') const { clamp } = require('@wizhut_tech/wizjs/math/numbers') // …/<namespace>/<area> — not the whole library if (isNil(value) || isEmpty(value)) { return defaultValue } clamp(value, 0, 10)
Functional composition
functools goes past Python's with compose and once.
const { compose } = require('@wizhut_tech/wizjs/lang/functools') // right to left: the last one runs first const slugify = compose( s => s.replace(/\s+/g, '-'), s => s.trim(), s => s.toLowerCase() ) slugify(' Hello World ') // → 'hello-world'
Object helpers
pick, omit, and a path-safe get that hands back your default instead of throwing.
const { pick, get } = require('@wizhut_tech/wizjs/lang/objects') const user = { id: 7, name: 'Ada', password: '…', address: { city: 'London' }, } pick(user, ['id', 'name']) // → { id: 7, name: 'Ada' } get(user, 'address.city') // → 'London' get(user, 'address.zip', 'n/a') // → 'n/a'
BBTreeMap: an ordered map, after a Rust B-tree
A Map that keeps its keys sorted, so you can walk them in order, take a range or ask for the first and the last. It follows bbtree, Xiangpeng Hao's Rust B-tree: each key becomes a number that sorts the same way, the prefix a node's keys share is skipped, and wide nodes are searched in a straight line. Then we measured which of its claims still hold in JavaScript.
const { BBTreeMap } = require('@wizhut_tech/wizjs/lang/btree') const prices = new BBTreeMap([ ['pear', 2], ['apple', 1], ['fig', 4], ]) prices.set('kiwi', 3) [...prices.keys()] // → ['apple', 'fig', 'kiwi', 'pear'] [...prices.range('b', 'l')] // → [['fig', 4], ['kiwi', 3]] prices.first() // → ['apple', 1]
| Keys | BBTreeMap | Classic B-tree | sorted-btree | Skip list | Map |
|---|---|---|---|---|---|
| Random integers | 202 | 543 | 283 | 3,582 | 79 |
| UUIDs | 358 | 773 | 748 | 3,120 | 150 |
| Article titles | 419 | 782 | 764 | 3,175 | 109 |
| JSON keys | 430 | 746 | 734 | 3,254 | 140 |
| URL paths | 911 | 1,002 | 966 | 3,139 | 167 |
Lower is faster. Half hits, half misses, on an Apple M4 Max with Node 26. The classic B-tree has Rust's standard node size, sorted-btree is the most used B-tree on npm, and Map keeps no order.
- 2.75× faster lookups than a classic B-tree with Rust's standard node size, for integers at ten million entries, and 2.35× for UUIDs: about what the Rust original reports.
- 14–18× faster than a skip list for integers, and keys inserted in ascending order go in at about 20 ns each, three times the classic tree's speed.
- Less than half the memory for number keys: 29 bytes an entry at ten million, against the classic tree's 72.
- What JavaScript cannot carry over. It has no SIMD and no prefetch, so asking for 64 keys at once gains nothing. String keys take more memory than a plain B-tree's, and URL paths, which differ only far past a shared prefix, come out even.
- No need for order? Use a Map. A hash table is still 2.4–5.5× faster for lookups.
Only the helpers that carry their weight
io.files- Small file helpers — loadFully reads a whole file in one call, no fs boilerplate.
lang.arrays- Practical array operations beyond the standard prototype — chunk, unique, flatten, compact, and accumulate for folding with named operators.
lang.checks- Type predicates: isNil, isEmpty, isString, isObject, isInteger and friends. Stricter than lodash's — arrays and functions are not objects here, and a number is never empty.
lang.flow- Control-flow helpers — if_exception runs a function, then your success callback, or your error callback with the error if either one throws.
lang.singleton- Register a factory under a name, then await getInstance anywhere: the first call builds the instance, and every later call gets the same one — even a call that arrives while it is still being built.
lang.functools- Python's functools in JS — partial, reduce and cache (also called memoize) — plus compose and once, which Python's does not have.
lang.itertools- Python's itertools as 18 lazy generators — chain, zip, range, enumerate, islice, groupby, product, batched, pairwise, and more — plus take, which returns an array. Works on any iterable.
lang.objects- pick, omit, and path-safe get — the three object helpers every codebase ends up writing.
lang.collections- The Python collections JS is missing — Counter with mostCommon and elements, and DefaultDict with a filling factory.
lang.btree- Ordered maps: BBTreeMap for string and number keys, built after a Rust B-tree, and the classic BTreeMap with a comparator for any other key. Walk the keys in order, take a range, ask for the first or the last.
math.numbers- Numeric utilities — clamp, true modulo, and the safe coercions toInteger and toNumber.
Questions Python developers ask about WizJS
- Is there an itertools for JavaScript?
- JavaScript has no itertools module, but WizJS has one:
lang/itertools, with 18 lazy generators under their Python names —count,cycle,chain,islice,groupby,product,pairwise,batchedand the rest, withzip,rangeandenumeratealongside — plustake, which returns an array. Wherever they take an iterable, any will do: arrays, strings, Maps, Sets or other generators. - How do I use Counter and defaultdict in JavaScript?
- Require both from
lang/collections.new Counter('banana')counts as Python's does; read a count withget, which gives 0 for anything unseen, and rank them withmostCommon(n). defaultdict isDefaultDict, sonew DefaultDict(Array)is Python'sdefaultdict(list):getfills a missing key as square brackets do, andpeekreads without filling. - Is there a SortedDict for JavaScript?
- Python's SortedDict comes from the sortedcontainers package; in WizJS the nearest thing is
BBTreeMapinlang/btree, a map that keeps string or number keys in order. It iterates in key order,range(lo, hi)walks the entries between two keys, andfirst()andlast()stand in forpeekitem(0)andpeekitem(-1). For keys of any other type,BTreeMaptakes a comparator. - Can I use import instead of require?
- Yes, in Node. WizJS is CommonJS, and Node lets an ES module import each area's functions by name:
import { cache } from '@wizhut_tech/wizjs/lang/functools'. From the package root only the default import works —import wizjs from '@wizhut_tech/wizjs'— so take names from an area rather than the root. - Does WizJS come with TypeScript types?
- No. WizJS is plain JavaScript and ships no type declarations, so TypeScript types its functions as
any. Understrict, each area you import also needs a one-linedeclare moduleof your own before the import compiles. - How is it different from lodash?
- It is small on purpose and not trying to become another lodash: Python's itertools, functools and collections under their Python names, plus a handful of everyday helpers — about 13 kB gzipped for the whole library, most of it the ordered maps. Its checks answer differently, too:
isObject([])andisEmpty(1)are false here and true in lodash. It is CommonJS, so don't count on tree-shaking; import one area to load less. - Does it work in the browser?
- It is tested on Node only. Nothing outside
io/filesuses a Node API, but WizJS is CommonJS and no browser or edge runtime is part of its tests — if you bundle it for one, check it there before you rely on it.
Ready to drop in.