my typescript everyday carry
environment-agnostic zero-dependency tools and utilities. by https://e280.org/
π΄ utensils
β³ async/fns
π£ subby
π§Ή housekeeping
π² rand
β
ok, err, result
𧬠hex and more
npm install @e280/stzimport {got, subby, Rand, hex} from "@e280/stz"- got β throw an error if the value is null or undefined.
const value = got(nullableValue)
- need β return a Map value, or throw an error if missing.
const value = need(map, "my_key")
- guarantee β get-or-create a Map value.
const value = guarantee(map, "my_key", () => 123)
- setEntries β set many Map entries at once.
setEntries(map, [["my_key1", 123], ["my_key2", 234]])
- count β iterate numbers.
for (const i of count(4)) console.log(i)
- grid β iterate columns and rows.
for (const [x, y] of grid(2, 4)) console.log(x, y)
- arraylimit β limit array length by dropping oldest.
const trimmed = arraylimit(["a", "b", "c", "d"], 2) // ["c", "d"] // returns new array if trimming happens, // otherwise returns the same array unchanged.
- pipe β run data through several fns.
const result = pipe(rawData) .to(parse) .to(validate) .to(normalize) .done()
- obmap β map over object entries.
const alpha = {alpha: 1, bravo: 2, charlie: 3}
obmap(alpha, n => n * 10) // {alpha: 10, bravo: 20, charlie: 30}
- obfilter β filter object entries.
obfilter(alpha, n => n > 1) // {bravo: 2, charlie: 3}
- deepFreeze β recursively Object.freeze an object tree.
const freezie = deepFreeze({alpha: 123}) freezie.alpha = 5 // throws error
- deepEqual β check if two object trees have the same primitive values.
deepEqual({alpha: 123}, {alpha: 123}) // true
- 'is' type guards β check the identity of things with proper type guards.
isHappy(0) // true if not null nor undefined. isSad(undefined) // true if null or undefined. isBoolean(true) // true isNumber(123) // true isString("hello") // true isBigint(123n) // true isArray([]) // true isObject({}) // true isFn(() => {}) // true isSymbol(Symbol()) // true
- time β durations and timestamps.
durations:timestamps after Date.now:seconds(1) // 1000 minutes(1) hours(1) days(1)
timestamps before Date.now:futureSeconds(1) futureMinutes(1) futureHours(1) futureDays(1)
pastSeconds(1) pastMinutes(1) pastHours(1) pastDays(1)
- nap β return a promise that resolves later.
await nap(1000) // sleep for one second.
- cycle β repeatedly call the fn back-to-back.
const stop = cycle(async() => { console.log("hello!", Date.now()) await nap(1000) // once per second. })
stop() // cancel the cycle.
- concurrent β await a group of named promises.
const {user, settings} = await concurrent({ user: myFetchUser(), settings: myFetchSettings(), })
- defer β a promise you can resolve/reject from outside.
const ready = defer<string>() ready.resolve("hello") await ready // "hello"
ready.reject(new Error("rejected"))
- collect β async iterable to array.
const entries = await collect(kv.entries())
- once β only execute the fn one time.
const init = once(() => connect()) init() init() // doesn't initialize twice.
- queue β make async fn calls work one-at-a-time.
const save = queue(async(myData: string) => myWriteData(myData)) await Promise.all([save("a"), save("b"), save("c")]) // actually executes sequentially.
- deadline β add an expiry timeout.
const connection = await deadline(10_000, connect) // throw DeadlineError after 10s unless connect resolves. // connect can be a promise or an async fn.
- debounce β dedupe calls over timeframe.
const search = debounce(250, async(s: string) => query(s)) search("l") search("lo") await search("lol") // last one actually runs.
- microbounce β dedupe calls in this microtask.
let count = 0 const run = microbounce(() => count++) const done = run() run() run() await done count // 1
- subby β create a subscriber fn.
const on = subby<[number]>()
const off = on(x => console.log(x))
on.publish(123) // 123
off() // stop listening.
- pubby β create a publisher fn.
const publish = pubby<[number]>()
const off = publish.on(x => console.log(x))
publish(123) // 123
off() // stop listening.
- π§ββοΈ 'subby' and 'pubby' give you the same goody-bag.
// limited 'on' and 'publish' fns without whole toolkit. const {on, publish} = subby()
const {publishAsync} = subby() await publishAsync(123) // awaits every listener (they can be async).
const [value] = await on.next() // wait for the next publish. // value is 123
on.set.size // direct access to the set of listeners.
- ev β event listeners.
import {ev} from "@e280/stz" const off = ev(window, { keydown: event => console.log(event), keyup: event => console.log(event), }) off() // removes both listeners
- disposer β cleanup resources.
more disposer tricks.
const dispose = disposer() dispose.schedule(() => console.log("dispose 1")) dispose.schedule(() => console.log("dispose 2")) dispose.schedule(cycle(myGameloop)) dispose.schedule(ev(window, {keydown: myKeydown})) dispose() // dispose everything backwards // *dispose ev* // *dispose cycle* // "dispose 2" // "dispose 1"
const myThing = dispose.own(new MyThing(), t => t.dispose()) // create a thing and schedule its dispose
const myThing = dispose.disposable(new MyThing()) // return and schedule a Disposable thing
const {dispose, schedule, own, disposable} = disposer() schedule(cycle(myGameloop)) const myThing = disposable(new MyThing) dispose()
- Rand β random utility.
const rand = new Rand(Math.random)
const rand = new Rand(mulberry(123)) // seeded pseudo-random number generator
rand.u32() // get a random unsigned 32-bit integer. // 3286174905 rand.roll(0.25) // 25% chance of true. // false rand.range(10, 20) // random float between two numbers. // 14.8591238 rand.integerRange(1, 6) // inclusive integer range. // 4 rand.index(7) // given array length, pick an array index. // 5 rand.pick(["a", "b", "c"]) // pick your poison. // "c" rand.select(2, ["a", "b", "c"]) // pick multiple. // ["a", "c"] rand.yoink(["a", "b", "c"]) // remove and return one array element. // "a" rand.extract(2, ["a", "b", "c"]) // yoink multiple. // ["b", "c"] rand.shuffle(["a", "b", "c"]) // random-sort array in-place. // ["c", "a", "b"]
- mulberry β seeded pseudo-random number generator.
const random = mulberry(123) random() // 0.7872516233474016 random() // 0.1785435655619949
- rand32 β crypto-random unsigned 32-bit integer.
rand32() // 3948271056
- hash32 β mix entropy into a 32-bit integer (fast, non-cryptographic).
hash32(123, "hello", 234, "world") // 1012994381
- ok β
Ok<Value>β indicate success.ok(123) // {ok: true, value: 123}
- err β
Err<E>β indicate failure.err("fail") // {ok: false, error: "fail"}
Result<Value, E>β indicate something might succeed or fail.let result: Result<number, "fail"> = ok(123) result = err("fail")
- getOk/getErr β get the value, or
undefined.getOk(ok(123)) // 123 getOk(err("fail")) // undefined getErr(ok(123)) // undefined getErr(err("fail")) // "fail"
- gotOk/gotErr β get the value, or throw.
gotOk(ok(123)) // 123 gotOk(err("fail")) // throws Error("fail") gotErr(ok(123)) // throws error gotErr(err("fail")) // "fail"
- hex β encode/decode hexadecimal data.
hex(bytes) // encode Uint8Array bytes to string. hex.toBytes(string) // decode string to bytes. hex.toInteger(string) // decode string as integer. hex.fromInteger(n) // encode integer as a string. hex.random(32) // generate random encoded string, 32 bytes.
// we have more than just hex. hex(bytes) // string base2(bytes) // string base36(bytes) // string base58(bytes) // string base62(bytes) // string base64(bytes) // string base64url(bytes) // string
- txt β text data.
txt(bytes) // convert utf8 bytes to string. txt.toBytes("hello") // convert string to utf8 bytes.
- bytes β uint8array utilities.
bytes.eq(bytesA, bytesB) // true if they're equal. bytes.random(32) // get 32 crypto-random bytes.
π§βπ» https://e280.org/