Skip to content
portablejsPublic

About

Web-standard building blocks for any JavaScript runtime.

Resources

Stars

29 stars

Watchers

0 watching

Forks

Repository files navigation

  Portable

Web-standard building blocks for any JavaScript runtime.

The web's missing standard library: fast, lightweight, composable packages, so you can write your code once and run it in browsers, Cloudflare Workers, Node 24+, Bun, and Deno.

Examples

Save a streamed OpenAI chat completion to a file as JSON Lines:

import * as Jsonl from '@portable/jsonl';
import * as Sse from '@portable/sse';
import * as Streams from '@portable/streams';

const response = await fetch('https://api.openai.com/v1/chat/completions', {
  method: 'POST',
  headers: { authorization: `Bearer ${apiKey}`, 'content-type': 'application/json' },
  body: JSON.stringify({ model, messages, stream: true }),
});
// `transcript` is a WritableStream<Uint8Array> that writes to a file.
await response.body!
  .pipeThrough(new Sse.DecoderStream())
  .pipeThrough(Streams.filter((event) => event.data !== '[DONE]'))
  .pipeThrough(Streams.map((event) => JSON.parse(event.data)))
  .pipeThrough(new Jsonl.EncoderStream())
  .pipeTo(transcript);

Strip photo metadata (GPS, camera, dates) before uploading:

import * as Exif from '@portable/exif';
import * as ImageSize from '@portable/image-size';

// `file` is a JPEG, PNG or WebP File from a file input or a drop.
const photo = Exif.strip(new Uint8Array(await file.arrayBuffer()));
const { width, height } = ImageSize.parse(photo);
await fetch(`/upload?width=${width}&height=${height}`, { method: 'POST', body: photo });

Packages

Streams and bytes
@portable/streamsCollects, combines, and transforms web streams and iterables, and converts Node streams to web streams.
@portable/linesSplits a stream of bytes or text into lines.
@portable/binaryReads and writes integers, varints, and strings in binary protocols, and splits streams into length-prefixed frames.
@portable/bytesHex encoding, concatenation, and constant-time comparison of Uint8Arrays.
@portable/errorsThe error classes every package throws. Errors.is and instanceof match them even when two copies of a package are installed.
@portable/compressgzip and deflate on the platform's CompressionStream, a decompression stream with an output limit, and CRC-32.
Encodings
@portable/base64base64 and base64url, with MIME line wrapping and streams.
@portable/quoted-printableQuoted-printable encoding and decoding, as functions and streams.
@portable/charsetEncodes text into legacy charsets such as Shift_JIS, GBK, Big5, and windows-1251.
@portable/idnaConverts domain names between Unicode and ASCII by UTS #46.
Data formats
@portable/csvDecodes and encodes CSV, as functions and streams.
@portable/jsonlDecodes and encodes JSON Lines streams.
@portable/json-streamSelects values from a large JSON document by JSONPath, and writes JSON from a tree of promises and streams.
Email
@portable/mailBuilds a raw email message, with attachments and inline images, as a stream.
@portable/mime-headersEncoded words, folding, parameters, and address lists in email headers.
@portable/mime-streamSplits a MIME message into a stream of headers and bodies and writes it back byte for byte.
@portable/smtpSends email over SMTP or LMTP from any runtime with a TCP socket API.
@portable/dkimSigns and verifies DKIM signatures.
@portable/spfChecks whether a client may send mail for a domain, using SPF.
@portable/dmarcChecks a message against the DMARC policy of its From domain.
@portable/arcSeals and verifies ARC sets.
@portable/mail-authChecks a received message's SPF, DKIM, DMARC, and ARC in one call, writes the Authentication-Results header, and ARC-seals it.
DNS and networking
@portable/dnsLooks up DNS records over DNS-over-HTTPS, behind a resolver interface that node:dns/promises also matches.
@portable/dns-packetEncodes and decodes DNS messages in wire format.
@portable/ipParses, formats, compares, and classifies IPv4 and IPv6 addresses and CIDR blocks.
@portable/rdapLooks up who holds a domain name, an IP address, or an AS number over RDAP.
@portable/socketsThe WinterTC Sockets API types for TCP and TLS clients, and a connect for Node, Bun, and Deno with the same signature as connect from cloudflare:sockets.
Crypto and auth
@portable/web-cryptoBytes-in, bytes-out helpers over crypto.subtle: digests, HMAC, HKDF, keys from seeds for Ed25519 and X25519, keys from PEM text, and ECDSA signature conversion.
@portable/passwordHashes and verifies passwords with PBKDF2 on WebCrypto.
@portable/webhooksVerifies and signs webhooks from Standard Webhooks senders, Stripe, GitHub, Slack, and other HMAC-SHA256 senders.
@portable/oauth1Signs OAuth 1.0a requests.
@portable/randomUnbiased random integers, strings, shuffles, and bytes, and a seeded generator for tests.
Realtime
@portable/websocketsA WebSocket that reconnects, with a readable stream of incoming messages and a writable stream for outgoing ones.
@portable/sseReads, writes, and reconnects server-sent event streams.
@portable/rpcTyped JSON-RPC 2.0 calls over a MessagePort, a window, a WebSocket, or HTTP, with streaming results.
@portable/web-pushSends Web Push notifications with payload encryption and VAPID.
Images
@portable/image-sizeReads an image's type and dimensions from its header, from bytes or a stream.
@portable/exifReads EXIF metadata from JPEG, TIFF, PNG, WebP, HEIC, and AVIF files, and strips it from JPEG, PNG, and WebP.
@portable/pngDecodes PNG files to RGBA pixels and encodes pixels as PNG.
Concurrency and timing
@portable/queueLimits how many async tasks run at once, with priorities and cancellation.
@portable/timersSleep, retry, poll, debounce, and throttle, all cancellable with an AbortSignal.
@portable/rate-limitRate limiting per key with GCRA, in memory, Workers KV, a Durable Object, or Redis over HTTP.
Logging
@portable/logLogs by namespace and level. Turn namespaces on with debug's DEBUG patterns, and change them without a reload.

How the packages work

Every package follows the same conventions, so once you've used one, you know how the others work:

  • Import a package as a namespace, such as import * as Csv from '@portable/csv', and call Csv.decode or new Csv.DecoderStream().
  • Bytes go in and come out as Uint8Array, and a Node Buffer is accepted. Functions that take text also take its bytes.
  • A streaming transform is a TransformStream that you pass to pipeThrough, as with TextDecoderStream.
  • Every async function and every stream takes a signal. Aborting it stops the fetch calls, timers, and nested streams underneath, and the call rejects with signal.reason, as fetch does.
  • An object that stays open, such as an SMTP client, a queue, or a WebSocket, has a close() method, a signal option that closes it, or both. It works with await using where the runtime has Symbol.asyncDispose.
  • Options that take a time, such as now, accept a Date, or a Temporal.Instant. Times in results are Date objects.
  • Errors extend the classes in @portable/errors. InvalidInputError means the data was malformed, and TypeError or RangeError means an argument was wrong.

Runtimes

The tests run in Chromium, Firefox, WebKit, Node 24, Node 26, Bun, and Deno. A check of every package also runs in workerd, the Cloudflare Workers runtime. Every package runs in Chrome 116, Firefox 124, and Safari 17.4 or later. Ed25519 signatures, in @portable/web-crypto, @portable/dkim, @portable/arc, and @portable/mail-auth, need Chrome 137 and Firefox 129, and X25519 keys in @portable/web-crypto need Chrome 133 and Firefox 130. @portable/smtp and @portable/sockets need a TCP socket API, so they run in Workers, Node, Bun, and Deno but not in browsers. The packages don't include polyfills, so if you support older browsers, you'll need to add them yourself.

License

MIT

About

Web-standard building blocks for any JavaScript runtime.

Resources

Stars

29 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages