Skip to content

hazae41/piscine

Repository files navigation

piscine

Create async pools with automatic retry

npm i @hazae41/piscine

Node Package 📦

Features

Current features

  • 100% TypeScript and ESM
  • No external dependency
  • Simple API
  • Automatic retry
  • Get a fast random value using Math's PRNG
  • Get a secure random value using WebCrypto's CSPRNG

Usage

WebSockets

Create a pool of WebSockets

import "@hazae41/symbol-dispose-polyfill";

import "@hazae41/disposable-stack-polyfill";

import { Disposer } from "@hazae41/disposer"
import { Box } from "@hazae41/box"
import { Pool } from "@hazae41/piscine"
import { Future } from "@hazae41/future"

async function openOrThrow(socket: WebSocket, signal: AbortSignal) {
  using stack = new DisposableStack()

  const future = new Future<void>()

  const onOpen = () => future.resolve()
  const onError = () => future.reject(new Error("Errored"))
  const onAbort = () => future.reject(new Error("Aborted"))

  socket.addEventListener("open", onOpen, { passive: true })
  stack.defer(() => socket.removeEventListener("open", onOpen))
  
  socket.addEventListener("error", onError, { passive: true })
  stack.defer(() => socket.removeEventListener("error", onError))

  signal.addEventListener("abort", onAbort, { passive: true })
  stack.defer(() => signal.removeEventListener("abort", onAbort))

  return await future.promise
}

const pool = new Pool<Disposer<WebSocket>>(async ({ pool, index, signal }) => {
  using stack = new Box(new DisposableStack())

  const raw = new WebSocket(`/api`)
  await waitOrThrow(raw, signal)

  const socket = new Disposer(raw, () => raw.close())

  const box = new Box(socket)
  stack.getOrThrow().use(box)

  const onClose = () => pool.restart(index)

  raw.addEventListener("close", onClose, { passive: true })
  stack.getOrThrow().defer(() => raw.removeEventListener("close", onClose))

  const unstack = stack.unwrapOrThrow()

  return new Disposer(box, () => unstack.dispose())
})

Start 5 of them

for (let i = 0; i < 5; i++)
  pool.start(i)

You can get the second one

const socket = await pool.getOrThrow(1)

You can get a random one using Math's PRNG

const socket = await pool.getRandomOrThrow()

socket.get().send("Hello world")

You can get a random one using WebCrypto's CSPRNG

const socket = await pool.getCryptoRandomOrThrow()

socket.get().send("Hello world")

You can iterate on them

for (const socket of pool)
  socket.get().send("Hello world")

Or grab all of them

const sockets = [...pool]

You can take a random one from the pool (and restart its index)

import { Mutex } from "@hazae41/mutex"

const mutex = new Mutex(pool)

{
  using socket = await Pool.takeCryptoRandomOrThrow(mutex)

  socket.get().send("Hello world")

  // Close socket
}