npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@qrpc/quiver

v0.0.3

Published

<p align="center"> <img src="quiver-logo.png" width="200px" align="center" alt="Quiver logo" /> <h1 align="center">Quiver</h1> </p> <br/> <p align="center"> <a href="https://opensource.org/licenses/MIT" rel="nofollow"><img src="https://img.shields.io/

Downloads

179

Readme

Quickstart

npm install @qrpc/quiver or yarn add @qrpc/quiver or pnpm add @qrpc/quiver

  1. Serve a function.
  2. Call the function.
  3. That's it!
// server.ts

import quiver from "@qrpc/quiver";
import { xmtp } from "./xmtp.js";

const q = quiver.q();
q.serve(() => 42);

console.log(`Server running at ${q.address}`)
// client.ts

import quiver from "@qrpc/quiver";

const q = quiver.q();
const client = q.client(process.env.SERVER_ADDRESS);
const answer = await client();

console.log(answer.data); // 42

That's all there is to it 🎉, you've just deployed a function to the internet, and called that function, in ~10 lines of code! No DNS, AWS, GCP, LOL, or WTF's involved! To learn more, keep on reading! To see more advanced examples, jump ahead to the Advanced Examples section. If you're wondering where the magic happens, jump to Under the Hood.

Table of Contents

Features

quiver is an extremely simple way to rapidly build and deploy services to the internet. It's powered by XMTP and inspired by trpc.

  • Type-Safe Client/Server
  • Type-Safe Middleware
  • Fluent builder APIs
  • End-to-End Encryption
  • Dead-Simple

Basic Usage

Functions

quiver lets you rapidly build secure client/server applications. The simplest server is just a function. A QuiverFunction can take 0, 1, or 2 arguments and optionally return a value. We always refer to the first argument as props and the second argument as context. Here's a simple example without:

// server.ts

import quiver from "@qrpc/quiver";

const q = quiver.q();

q.serve((props: { a: number, b: number }) => {
  return add(props);
});

Routers

You'll probably want to serve more than just a single function. You can do this by using a QuiverRouter. quiver provides a type-safe fluent-style builder API for constructing routers. Here's a simple example:

// server.ts

import { q } from "./q";

const router = q.router()
  .function("a", () => "a")
  .function("b", () => "b")

q.serve(router);

And your client can call these functions:

// client.ts

import { q } from "./q";

const client = q.client(process.env.SERVER_ADDRESS);

const a = await client.a(); // { data: "a" }
const b = await client.b(); // { data: "b" }

Routers can of course be nested into a tree structure. Here's an example:

// router.ts

import { q } from "./q";

const hello = q.router()
  .function("a", () => "hello from a")
  .function("b", () => "hello from b")

const goodbye = q.router()
  .function("a", () => "goodbye from a")
  .function("b", () => "goodbye from b")

export const router = q.router()
  .router("hello", hello)
  .router("goodbye", goodbye)

And now your client mirrors the structure of the server:


// client.ts

import { q } from "./q";

const client = q.client(process.env.SERVER_ADDRESS);

await client.hello.a(); // { data: "hello from a" }
await client.hello.b(); // { data: "hello from b" }
await client.goodbye.a(); // { data: "goodbye from a" }
await client.goodbye.b(); // { data: "goodbye from b" }

Middleware and Context

quiver provides a simple but powerful middleware system. A QuiverMiddleware is a function that takes 0 or 1 arguments and optionally returns an object. We always refer to the argument as context. Here's a simple example:


// middleware.ts

import { q } from "./q";

const logger = q.middleware(ctx => {
  console.log(ctx);
});

We can attach middleware to router and functions with use:

import { q } from "./q";
import { logger, timestamp } from "./middleware";
import { fn } from "./fn";
import { router } from "./router";

const fnWithTimestamp = fn.use(logger);
const routerWithLogger = router.use(logger);
const root = routerWithLogger.function("fn", fnWithTimestamp);
q.serve(root);

When a quiver server receives a request, it derives a default context object from the request and then passes it through the server's middleware. More details on this process can be found in the Middleware section.

TypeScript!

quiver's entire backend API is fully type-safe by default as long as you annotate all arguments. quiver's client API (q.client) is also fully type-safe whenever you provide the backend's type to the client. Here's an example of how to provide the backend's type to the client:


// router.ts

import { q } from "./q";

const router = q.router()
  .function("a", (i: { name: string }) => `hello, ${i.name}`)
  .function("b", () => "hello from b")

// Export the type of the router

export type Router = typeof router;

q.serve(router);

// client.ts

// Import the Router type
import type { Router } from "./router";
import { q } from "./q";

// Notice the generic here.
const client = q.client<Router>(process.env.SERVER_ADDRESS);

Now your client is type-safe! If you try to call a function that doesn't exist, you'll get a TypeScript error, if you pass the wrong arguments, you'll get a TypeScript error, and the return value's data field will be correctly typed!

XMTP Network Client

So far in all the examples, an XMTP network client is created inside our initial call to quiver.q(). This means that your server is listening to a random address. You'll probably want to re-use the same address (at least in production). You can do this by manually initializing XMTP and passing it to quiver. Here's how:

// server.ts

import quiver from "@qrpc/quiver";

const xmtp = quiver.x({ init: { key: process.env.XMTP_SECRET_KEY } });

const q = quiver.q({ xmtp });

q.serve(() => 42);

Now the server will be running at whatever address corresponds to your XMTP_SECRET_KEY, and you can call it:

// client.ts

import quiver from "@qrpc/quiver";

const quiver = quiver.q();

const client = quiver.client(process.env.SERVER_ADDRESS);

const answer = await client(); // { data: 42 }

Middleware Guide

quiver supports a simple but powerful type-safe middleware API. A QuiverMiddleware is essentially a function that optinally takes a context object and optionally returns a context object. When a middleware takes a context object, we say it "reads" from the context. When a middleware returns a context object, we say it "writes" to the context. We think of it this way because each middleware's return value is merged into the context which it receives.


// middleware.ts

import { q } from "./q";

const logger = q.middleware(ctx => {
  console.log(ctx);
});

const timestamp = q.middleware(() => {
  return {
    timestamp: Date.now(),
  };
});

To use a middleware in your server, you attach it to a QuiverRouter or QuiverFunction. Here's an example with a router:


import { logger } from "./middleware";

const router = q.router()
  .use(logger)
  .function("a", () => "a")
  .function("b", () => "b")

quiver's middleware system is type-safe. If you try to bind incompatible routes to a router with middleware, you'll get a TypeScript error:


import { q } from "./q";

const passesAString = q.middleware(ctx => {
  return {
    a: "a",
  };
});

const needsANumber = (i: undefined, ctx: { a: number }) => {
  // ...
}

const router = q.router()
  .use(passesAString)
  // Boom! TypeScript error!
  .function("a", needsANumber)

Merging Middleware

Middleware can be merged in a type-safe manner using mw.extend(other) and mw.pipe(other). extend can be thought of as "parallel merge" and pipe can be thought of as "sequential merge". Some examples:


import { q } from "./q";

const a = q.middleware(() => {
  return { a: Math.random(), };
});

const b = q.middleware(() => {
  return { b: Math.random(), };
});

const sum = q.middleware((ctx: { a: number, b: number }) => {
  return {
    sum: ctx.a + ctx.b,
  };
});

export const merged = a.extend(b).pipe(sum);

API Reference

TODO

Quiver

QuiverRouter

QuiverFunction

QuiverClient

QuiverMiddleware

QuiverProvider

Off-the-Shelf Middlewares

From the quiver team:

TODO

From the community:

TODO

Advanced Examples

TODO

Check out these runnable examples:

  • Hello, world!
  • Using quiver with React
  • ENS authentication
  • Peer-to-peer, serverless Tic-Tac-Toe
  • Type-Safety

If you have a use-case in mind, and are wondering how it might work, don't hesitate to open an issue, join the discord, or DM @killthebuddha_ on X.

Under the Hood

TODO

Quiver is built on top of the superb XMTP messaging protocol. XMTP provides out-of-the-box end-to-end encrypted messaging.

Roadmap

TODO

Right now we're currently on the path to v0.

If you have a feature (or bugfix) request, don't hesitate to open an issue or DM @killthebuddha_ on X.