-
Notifications
You must be signed in to change notification settings - Fork 0
Core API
The default export io is the whole library: a callable, the verbs, and the modifier namespaces
(io.full, io.stream) and services (io.cache, io.track, …).
import io from 'double-meh';
const data = await io.get('https://api.example.com/x');get, head, post, put, patch, delete (+ del / remove aliases), options — as methods
on io and as named ESM exports. Exotic verbs via io.makeVerb('REPORT').
The DELETE aliases. All three names — io.delete, io.del, io.remove — are the same verb.
As a method delete is unproblematic, but it cannot be a bare named ESM export
(import {delete} is a syntax error), so the named exports are del and remove; both ship, pick
whichever reads better and stay consistent:
import {del, remove} from 'double-meh'; // named-import land
await io.delete('/things/7'); // method land — all three are the same functionSignature: verb(url, data?, options?).
-
url— a string, aURL, or an options bag (a reusable endpoint descriptor). -
data— the frequent optional: the query for reads, the body for writes. -
options— the rare override bag (as,ifMatch,stream,cache,fetch, …), typed to excludeurl.
For reads, null/undefined data → no query. For writes, undefined → no body, but null is a
valid JSON-null body. See Requests & options.
Query plus body on a write. The positional data on a write is the body, so the query rides
options.query — no tag functions or URL string-building needed (heya/io used a template-tag
trick here; double-meh lowers it like every other intent):
await io.post('/units', {name: 'unit-1'}, {query: {dept: deptId}});
// → POST /units?dept=42 with the JSON body {"name":"unit-1"}| Call | Returns |
|---|---|
io.get(url) (any bare verb) |
the parsed data |
io.full.get(url) |
the full envelope {data, status, ok, headers, response, …}
|
io.stream.get(url) |
a Web ReadableStream (GET-only) — see Streaming
|
io.stream.put(url, opts) |
a streaming {writable, readable, response} duplex (also .post, .patch) |
io.records.get(url) |
an async iterable of parsed records (JSONL / json-seq; also .post) |
io.sse(url) |
an async iterable of Server-Sent Events, with reconnect |
io.head(url) / io.options(url)
|
the full envelope (metadata verbs — no body, so data is undefined) |
The bare verb is the envelope's .data: io.get(url) ≡ (await io.full.get(url)).data. Options
tune behavior, never the return type.
In TypeScript the verbs are generic: io.get<T>(url) → Promise<T>, io.full.get<T>(url) →
Promise<Envelope<T>>; io.head / io.options return Promise<Envelope>.
io(options) is the low-level callable; the verbs are sugar. Because url may be an options bag, a
reusable endpoint is natural — the bag is spread (never mutated), and the 3rd arg overrides it
per call (scalars replace; headers/query merge per key; url stays from the 1st arg):
const endpoint = {url: '/things/7', headers: {authorization: token}};
await io.get(endpoint); // GET with the auth header
await io.put(endpoint, body, {ifMatch: tag}); // reuse it for a conditional PUTio carries the services and the registration seams:
-
Services: cache, track, retry,
mock — each with
.attach()/.detach()and per-request options (cache and track are on by default for GETs; passcache: false/track: falseto opt out). -
Inspectors:
io.inspect.request(fn, match?)andio.inspect.response(fn, match?)rewrite requests / envelopes;matchscopes the inspector to matching request URLs — a URL prefix string, aRegExp, or a predicate(url) => boolean. The registries (io.requestInspectors,io.responseInspectors) hold{fn, match}entries (see Cookbook: requests & responses). -
Processors:
io.registerData(processor)(encode request bodies by type),io.registerMime(processor)(decode responses by content-type). -
Transports:
io.registerTransport(name, fn),io.defaultTransport— see fetch transport. -
MIME registry:
io.mimeTypes— the mutable alias table behind theasoption. -
Per-API defaults:
io.defaults(match?, bag)— scoped option defaults merged below the endpoint descriptor and per-call options; see Requests & options.
io.on(event, fn) / io.off(event, fn) / io.emit(event, ...args) — a tiny synchronous emitter
(see events). The library emits: 'request' (request, ctx) when a network run starts,
'success' (envelope, ctx), 'failure' (error, ctx), 'retry' ({attempt, response, error}, ctx)
per retry attempt, and 'ready' after the code-forward drain.
io.inFlight counts active network runs. Note: a cache hit still runs the pipeline, so it emits
request/success.
The default export is the shared, fully-configured instance. io.create() returns an isolated
instance with the same equipment (own registries, services, cache) and none of the parent's
configuration — a clean slate. createIO() (a named export) returns a bare pipeline — no
transport, no services — for minimal builds that wire their own. Error classes are shared across
instances, so one instanceof catch serves them all. When to isolate vs configure the shared
instance: Cookbook: multiple APIs.
io.run(options) (the raw pipeline, returns the envelope — takes fully-assembled options, so
io.defaults bags are not applied),
io.toEnvelope(response, options) (wrap a Response you already have),
io.makeKey(options) / io.buildUrl(options) (see keys & URLs), io.makeVerb(method).
Error classes ride on io too: io.IOError (the base), io.FailedIO, io.TimedOut,
io.BadStatus, plus the isAbort(error) named export — see Response envelope.
Guides
Concepts
Services
Modules
- fetch transport
- keys & URLs
- helpers
- records
- sse
- storage backends
- compression encoders
- Service Worker integration
Cookbook