prelude

Guides on this site are the repository READMEs; the API reference is generated from the sources. AI agents: npx skills add mirek/prelude or see the prelude skill.

prelude monorepo

A pnpm workspace containing small, focused TypeScript utility packages published under the @prelude/* scope.

What this repository contains

This repository is organized as a monorepo. Every directory under packages/* is a workspace package with its own package.json; pnpm manifests:check fails on a directory without one, because the build, typecheck and pack scripts discover packages by manifest and would otherwise skip it silently.

Quick start

Prerequisites

Install dependencies

pnpm install --frozen-lockfile

Run the CI-equivalent quality gate

pnpm verify

pnpm verify runs the same checks as GitHub Actions, in this order:

  1. pnpm manifests:check — verifies normalized package manifests, exports, and workspace TypeScript configs.
  2. pnpm release-hooks:check — rejects package-local version, push, or publish hooks.
  3. pnpm makefiles:check — rejects package recipes that build with tsc directly or use unquoted recursive globs.
  4. pnpm docs:check — verifies package READMEs, license references, manifest descriptions and the package index below.
  5. pnpm test:check — rejects unseeded randomness and unsupported node:test timeout arguments in the default suite.
  6. pnpm lint — type-aware linting across packages.
  7. pnpm typecheck — checks every package library and test TypeScript project independently.
  8. pnpm test — runs all colocated *.test.ts files through Node's test runner.
  9. pnpm test:scripts — runs the tests of the repository scripts themselves.
  10. pnpm build — rebuilds every package with a tsconfig.lib.json from clean source.
  11. pnpm pack:check — packs every public package and installs the tarballs into an isolated consumer project.

Release a change

Every pull request adds a changeset (pnpm changeset, or pnpm changeset --empty when nothing publishable changed); CI rejects pull requests without one. Merged changesets accumulate in an automated chore(release): version packages pull request; merging it bumps versions and changelogs, and the Publish workflow then publishes every not-yet-published version to npm with provenance and tags it <package>@<version>. See .changeset/README.md.

Concurrency tests use explicit barriers, fake schedulers, and awaited task settlement rather than random sleeps or eventual polling. Optional randomized stress coverage must use a reproducible seed.

The data structures with the largest state spaces (rb-tree and its Bag/Map, radix-trie, range-set, sorted-array, set, channel, serial-queue) also have model-based property tests (packages/*/src/model.test.ts, laws.test.ts) built on the private @prelude/testing package: checkTrace generates operation traces from a seed, replays them against the structure and a naive reference model with every invariant checked after each step, and on failure shrinks the trace and reports seed, trial and the minimal operation sequence. The default suite runs a bounded number of trials; SLOW_TESTS=<factor> multiplies them, which the weekly Stress workflow (.github/workflows/stress.yml, also runnable by hand) does with a factor of 20.

The package check verifies declared entry points, declaration resolution, root and subpath runtime imports, workspace dependency rewriting, the externally published TypeScript configs, and exclusion of development-only files. Isomorphic packages are additionally imported with Node's platform globals removed and every Node builtin unresolvable, and their declarations are type-checked without @types/node. Every publishable library also builds from source during its own prepack lifecycle.

GitHub Actions runs these checks independently on Node.js 22 (the supported floor), 24 and 26. The aggregate CI job is suitable as the required branch-protection check.

Supported runtimes

Every published @prelude/* package declares "engines": { "node": ">=22" }; the root manifest declares the same. Node 22 is the oldest release line still in maintenance, and CI runs the full quality gate on it and on the current release lines, so the floor is exercised, not inferred.

Clean TypeScript build artifacts

make clean

Cancellation

Long-lived asynchronous operations across the packages share one cancellation convention, built on the platform's AbortSignal:

Packages following the convention: function (sleep, timeout, eventually, throttle), emitter (eventually, eventuallyIf), channel (read, maybeRead, write, maybeWrite, writeIgnore, select/selectNext/selectAsync, after, ofIterable, ofAsyncIterable), serial-queue (pushWith), progress (start, also disposable), remote-clock (midSeconds, midSecondsInterval), async-generator (sleep, jitter, ofInterval, map, tap, consume), plus actor/remote-actor (ask) and jsonrpc (call) which already did. Operations without a signal are either instantaneous or already have an explicit stop (Emitter.on returns an unregister function, throttle returns a function whose pending call the signal drops, serial-queue.rejectAll empties a queue).

Package index

Generated from the workspace manifests by pnpm docs:write; pnpm docs:check fails when it drifts.

Directory Package Description
packages/actor @prelude/actor Actor module: stateful message processors with ask/reply, bounded mailboxes, failure directives and supervision hooks.
packages/array @prelude/array Array utilities: safe indexing, sampling and shuffling, sorting and searching, grouping, swap-delete and vector helpers.
packages/assert @prelude/assert Composable runtime assertions that narrow unknown values to typed ones and throw AssertionError with the failing path; built on @prelude/validation.
packages/async-generator @prelude/async-generator Composable async iterable transforms: map, filter, batch, window, buffered and concurrent processing with backpressure.
packages/channel @prelude/channel Go-style channels for async code: buffered or unbuffered, async-iterable, with select over reads and writes.
packages/cmp @prelude/cmp Sound comparators: strict -1
packages/emitter @prelude/emitter Type-safe event emitter with once, predicate-filtered listeners and promise-based waits with timeouts.
packages/eq @prelude/eq Structural equality combinators for primitives, arrays, tuples, records and partial objects.
packages/err @prelude/err Errors with a severity and a code, plus helpers to create, inspect and rethrow them consistently.
packages/fs @prelude/fs Node.js file-system helpers: JSON and string read/write, existence checks and deterministic depth-first traversal.
packages/function @prelude/function Function utilities: pipe, memoize, throttle, timeout, retry (eventually), sleep, serial execution and logic combinators.
packages/generator @prelude/generator Iterable and generator utilities: lazy map, filter, flatMap, batch, group, permutations, primes and other transforms over sync iterables.
packages/json @prelude/json (private) JSON encoding and decoding of non-JSON-native values (Set, Map, Date, RegExp, bigint, errors) through tagged coders.
packages/jsonrpc @prelude/jsonrpc JSON-RPC 2.0 client and request handling over any message transport, with request timeouts, abort signals and typed payloads.
packages/log @prelude/log Lightweight logger with severity levels, namespaces, pluggable targets and environment-variable level control.
packages/parser @prelude/parser Parser combinators over string readers with location-aware failures, plus an RFC 8259 JSON grammar.
packages/predicate @prelude/predicate Type-guard combinators for primitives, objects, arrays, tuples, records and unions; built on @prelude/validation.
packages/prelude @prelude/prelude Low-level primitives shared by @prelude/* packages: pipe and common utility types.
packages/progress @prelude/progress Terminal progress display for Node.js: multi-worker progress bars, spinners and percentages on stdout.
packages/radix-trie @prelude/radix-trie Radix (compressed prefix) trie for string sets: insert, membership and shortest/longest prefix matching.
packages/range-set @prelude/range-set Sets of value-carrying ranges over ordered keys with union, intersection and difference under pluggable value merges.
packages/rb-tree @prelude/rb-tree Persistent red-black tree with duplicate counts, range counting and Bag/Map wrappers, with exported invariant checks.
packages/refute @prelude/refute Validators that return ok/refuted results with a reason instead of throwing, with predicate and assertion interpreters; built on @prelude/validation.
packages/remote-actor @prelude/remote-actor Remote actor module: send/ask to an actor over any message transport (MessagePort, workers, sockets).
packages/remote-clock @prelude/remote-clock Clock synchronisation against a remote time source: offset estimation, remote now/date and mid-second ticks.
packages/repl @prelude/repl Node.js REPL helpers: run code snippets extracted from Markdown in a sandbox with builtin modules and globals.
packages/semver @prelude/semver Semantic Versioning 2.0 parsing, comparison and precedence ordering.
packages/serial-queue @prelude/serial-queue Promise-returning serial task queue: pushed work runs one at a time, in order, with bulk rejection.
packages/set @prelude/set Set helpers over the native Set: union, intersection, difference, equality, numeric ranges and sorted/shuffled views.
packages/sorted-array @prelude/sorted-array Arrays kept sorted by a comparator with binary search, insert, insert-ignore and upsert.
packages/string @prelude/string String utilities: blank checks, case conversion, indentation, truncation, line operations, search-replace and edit distance.
packages/supervisor @prelude/supervisor Supervisor module: restart strategies (one-for-one, all-for-one, rest-for-one) and restart limits for @prelude/actor.
packages/testing @prelude/testing (private) Workspace-internal test helpers: seeded pseudo-random generation and trace-based model checking with shrinking.
packages/tsconfig @prelude/tsconfig Shared TypeScript configurations for the prelude monorepo.
packages/validation @prelude/validation Shared validator core behind @prelude/assert, @prelude/predicate and @prelude/refute: one implementation of the primitive, container and combinator checks producing structured, path-carrying failures.
packages/wait-group @prelude/wait-group Go-style WaitGroup: count outstanding work and await completion, with invalid-counter protection.
packages/xml @prelude/xml XML 1.0 parser with namespace processing, precise error locations and JSON conversion helpers; no DTD processing or external resources.

Package documentation

Every package ships Readme.md and License.md (CC0-1.0). pnpm docs:check (part of pnpm verify) enforces the shared shape:

A minimal package README therefore looks like:

# Foo module

One or two sentences on what the package is for and when to reach for it.

# Usage

    npm i -E @prelude/foo

    import * as Foo from '@prelude/foo'
    Foo.bar(1)

# License

This package is dedicated to the public domain under [CC0 1.0](./License.md).

Package manifests get repository, homepage, bugs, files, exports and types from pnpm manifests:write, derived from the package directory, so they cannot drift from the repository layout.

Documentation site and AI agents

The documentation site at https://mirekrusin.com/prelude/ is built by pnpm site:build (scripts/build-site.mjs) from the Markdown already in this repository — this README, every packages/*/Readme.md and CHANGELOG.md — plus a TypeDoc API reference under /api/, and is deployed by the Pages workflow on every push to main. Nothing on the site is written twice: package pages are the package READMEs.

For language models the site also publishes:

Install the skill into an agent (Claude Code, Codex, Cursor and others supported by skills):

npx skills add mirek/prelude

The well-known index is published under the site's base path (/prelude/.well-known/agent-skills/index.json); origin-root discovery (npx skills add https://<domain>) needs the site on its own domain — set SITE_URL accordingly in the Pages workflow, nothing else changes.

pnpm site:preview builds the site without the API reference (a few seconds instead of a minute) into site/, which is ignored by git.

License

The repository root is licensed under CC0-1.0. Individual packages may declare their own licenses in package-level files/manifests.