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 🙏

© 2026 – Pkg Stats / Ryan Hefner

@aredotna/sdk

v0.0.4

Published

Official TypeScript SDK for the Are.na API

Readme

@aredotna/sdk

Official TypeScript SDK for the Are.na API.

Are.na is a platform for connecting ideas and building knowledge. Users collect images, text, links, files, and channels into collaborative knowledge networks.

Install

pnpm add @aredotna/sdk

This package is ESM-only and requires Node 20+ or a modern browser/runtime with fetch.

Quickstart

Public reads do not require authentication:

import { createArena } from "@aredotna/sdk";

const arena = createArena();
const channel = await arena.channels.get("arena-influences");
const firstPage = await arena.channels.contents(channel.slug, { per: 24 });

Authentication

Most public data can be read without a token. Authenticated requests can see private content you have access to and can create, update, or delete resources.

Use a personal access token from are.na/settings/personal-access-tokens:

const arena = createArena({
  token: process.env.ARENA_TOKEN,
});

You can also provide a lazy token getter, which is useful in React server components, server actions, or API routes:

const arena = createArena({
  token: async () => session.accessToken,
});

Core Concepts

Channels are collections of blocks and other channels. Blocks are pieces of content: text, images, links, embeds, or attachments. Connections place a block or channel inside a channel. Users and groups own content and can follow users, channels, and groups.

The SDK exposes these concepts as stable resource groups:

await arena.channels.get("my-channel");
await arena.blocks.get(123);
await arena.connections.create({
  connectable_id: 123,
  connectable_type: "Block",
  channel_ids: [456],
});
await arena.users.get("damon-zucconi");
await arena.groups.get("some-group");

Pagination

Are.na channels and users can contain thousands of items. Fetch the first page quickly, render partial data, and load more pages only when the user scrolls or explicitly asks for more.

const pages = arena.channels.paginateContents("arena-influences", { per: 24 });

const firstPage = await pages.next();
if (!firstPage.done) {
  render(firstPage.value.data);
}

async function loadMore() {
  const nextPage = await pages.next();
  if (!nextPage.done) {
    append(nextPage.value.data);
  }
}

List endpoints also accept explicit page and per parameters. per defaults to 24 and maxes out at 100, but avoid setting per: 100 and crawling every page at startup. Use response metadata such as meta.has_more_pages and meta.next_page to request the next page on demand.

The lower-level paginate() helper also works with generated operations from @aredotna/sdk/api.

OAuth + PKCE

Register an OAuth application at are.na/oauth/applications.

The SDK exposes OAuth primitives from @aredotna/sdk/oauth. Apps still own routing, short-lived PKCE/state storage, and long-lived session persistence.

import {
  OAuth,
  OAuthMissingCodeError,
  OAuthProviderError,
  OAuthStateMismatchError,
  generatePKCE,
  generateState,
  parseOAuthCallback,
} from "@aredotna/sdk/oauth";

const oauth = new OAuth({ clientId, redirectUri });
const pkce = await generatePKCE();
const state = generateState();

// Store transient values before leaving your app. sessionStorage is usually the
// right browser primitive for one-tab OAuth transactions.
sessionStorage.setItem("arena:oauth:codeVerifier", pkce.codeVerifier);
sessionStorage.setItem("arena:oauth:state", state);

location.assign(
  oauth.authorizeUrl({
    codeChallenge: pkce.codeChallenge,
    scope: "write",
    state,
  }),
);

On your callback page:

import { createArena } from "@aredotna/sdk";
import {
  OAuth,
  OAuthMissingCodeError,
  OAuthProviderError,
  OAuthStateMismatchError,
  parseOAuthCallback,
} from "@aredotna/sdk/oauth";

const oauth = new OAuth({ clientId, redirectUri });
const callback = parseOAuthCallback(window.location.href);

if (!callback.ok) {
  if (callback.error === "missing_code") {
    throw new OAuthMissingCodeError();
  }
  throw new OAuthProviderError(callback);
}

const codeVerifier = sessionStorage.getItem("arena:oauth:codeVerifier");
const expectedState = sessionStorage.getItem("arena:oauth:state");

if (callback.state !== expectedState) {
  throw new OAuthStateMismatchError({
    expectedState: expectedState ?? undefined,
    state: callback.state,
  });
}

const token = await oauth.exchangeCode({
  code: callback.code,
  codeVerifier: codeVerifier ?? undefined,
  expectedState: expectedState ?? undefined,
  state: callback.state,
});

sessionStorage.removeItem("arena:oauth:codeVerifier");
sessionStorage.removeItem("arena:oauth:state");

const arena = createArena({ token: token.access_token });

parseOAuthCallback() never throws. It returns either { ok: true, code, state } or { ok: false, error, errorDescription, state }, so callback screens can render provider errors without string parsing.

For confidential server-side clients:

const token = await oauth.exchangeCode({ code, clientSecret });
const appToken = await oauth.clientCredentials({ clientSecret });

The SDK does not persist access tokens. Store application sessions and tokens according to your app's security model. For browser PKCE transactions, prefer sessionStorage over localStorage because the verifier/state are short-lived redirect transaction values.

Uploads

The API upload flow is: presign with Are.na, PUT bytes to S3, then create a block with the temporary S3 URL. The SDK provides a one-shot helper:

const block = await arena.uploads.createBlock({
  file,
  channels: [{ id: 123 }],
  block: {
    title: "Reference image",
    description: "A short note about why this image belongs in the channel.",
  },
});

You can also upload without creating a block:

const uploaded = await arena.uploads.upload(file);
console.log(uploaded.url);

Upload progress is best-effort. Runtimes that cannot observe upload progress report start and completion only.

Errors And Rate Limits

The main SDK facade throws normalized errors:

import { ArenaApiError, ArenaRateLimitError } from "@aredotna/sdk";

try {
  await arena.channels.get("private-channel");
} catch (error) {
  if (error instanceof ArenaRateLimitError) {
    console.log(error.retryAfter);
  } else if (error instanceof ArenaApiError) {
    console.log(error.status, error.details);
  }
}

Rate-limit headers are captured on the Arena instance:

await arena.ping();
console.log(arena.rateLimit?.tier, arena.rateLimit?.limit);

Opt into automatic 429 retry:

const arena = createArena({
  retry: { respectRateLimits: true, maxRetries: 1 },
});

React

This package does not ship hooks. Use the SDK with your data fetching library of choice:

import { useQuery } from "@tanstack/react-query";
import { createArena } from "@aredotna/sdk";

const arena = createArena();

function Channel({ id }: { id: string }) {
  const query = useQuery({
    queryKey: ["channel", id],
    queryFn: ({ signal }) => arena.channels.get(id, { signal }),
  });

  return <h1>{query.data?.title}</h1>;
}

Raw Generated API

For complete OpenAPI-shaped access, import from @aredotna/sdk/api:

import { createClient, getChannel } from "@aredotna/sdk/api";

const client = createClient({ token });
const result = await getChannel({ client, path: { id: "arena-influences" } });

if (result.error) {
  console.error(result.error);
}

The raw API preserves Hey API's { data, error, response } result-object style. The main @aredotna/sdk facade is the stable ergonomic surface and throws normalized errors.

Regenerating From OpenAPI

pnpm gen:fetch
pnpm gen

The source spec is https://api.are.na/v3/openapi.json. The developer docs are available at are.na/developers/explore.