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

@productcraft/agora

v0.0.6

Published

Social-as-a-service backend — communities, posts, ranked feeds, stories, direct conversations, notifications, moderation, flags via ProductCraft Agora. Generated from the production OpenAPI spec.

Readme

@productcraft/agora

Typed Node.js SDK for ProductCraft Agora — social-as-a-service backend: communities, posts, ranked feeds, stories (with polls + close-friends), direct conversations, notifications, moderation, flags. Generic actor / object / edge primitives.

npm install @productcraft/agora

Server-side only. Customer-facing apps integrate via a backend (BFF pattern) that holds the workspace PAK (pcft_live_…).

Two caller contexts

Agora's surface splits two ways — both reach the same data but differ in auth + URL shape:

| Caller | URL prefix | Auth | What you do | |---|---|---|---| | PlatformUser admin | /v1/workspaces/{workspaceId}/... | cookie or PAK | Create communities, run moderation, read analytics. | | Customer backend | /v1/communities/{communityId}/... | PAK + X-Acting-As: <actor_id> header | Day-to-day reads/writes on behalf of an EndUser. |

The customer backend acts on behalf of one of its EndUsers via the X-Acting-As header — the SDK passes whatever you put in headers straight through, so a per-request wrapper that injects the acting actor id is idiomatic.

Quick start — create a community (admin)

import { Agora } from "@productcraft/agora";

const agora = new Agora({
  auth: { type: "apiKey", key: process.env.PCFT_KEY! },
});

const { data, error } = await agora.client.POST(
  "/v1/workspaces/{workspaceId}/communities",
  {
    params: { path: { workspaceId: "<workspace-uuid>" } },
    body: { display_name: "Founders Club", slug: "founders" },
  },
);

Quick start — post on behalf of an EndUser (customer backend)

const actor_id = "<actor-uuid>"; // the EndUser's Agora actor id

const { data, error } = await agora.client.POST(
  "/v1/communities/{communityId}/posts",
  {
    params: {
      path: { communityId: "<community-uuid>" },
      header: { "X-Acting-As": actor_id },
    },
    body: {
      // `actor_id` is required in the body too — the header sets caller
      // identity; the body field is the author of the post (typically
      // the same UUID, but allowed to differ on admin-impersonation flows).
      actor_id,
      text: "Hello, world!",
      visibility: "public",
    },
  },
);

Agora's wire is snake_case both at the DTO level and in TS types — there's no name translation layer for this surface. Use snake_case keys in body to match what the API actually accepts.

Configuration

new Agora({
  auth: { type: "apiKey", key: "pcft_live_..." }
      | { type: "bearer", token: "eyJ..." }
      | { type: "cookie", value: "auth_token=..." },
  baseUrl: "https://agora.example.test",  // optional override
  fetch: customFetch,                      // optional
});

Common operations

Posts + feeds

// Create a post
await agora.client.POST(
  "/v1/communities/{communityId}/posts",
  { params: { path: { communityId } }, body: { text: "..." } },
);

// Get the actor's ranked home feed
await agora.client.GET(
  "/v1/communities/{communityId}/actors/{actorId}/feed",
  { params: { path: { communityId, actorId } } },
);

// Discover (community-wide ranked feed, not personalised to follows)
await agora.client.GET(
  "/v1/communities/{communityId}/discover-feed",
  { params: { path: { communityId } } },
);

// React / unreact / bookmark / quote / repost — same shape, different paths

Stories

// Post a story (24h TTL)
await agora.client.POST(
  "/v1/communities/{communityId}/stories",
  { params: { ... }, body: { media_url: "...", visibility: "close_friends" } },
);

// Story tray (the bubble row at the top of the home screen)
await agora.client.GET(
  "/v1/communities/{communityId}/actors/{actorId}/story-tray",
  { ... },
);

Social graph

// Follow / unfollow
await agora.client.PUT(
  "/v1/communities/{communityId}/follows/{srcActorId}/{dstActorId}",
  { ... },
);

// Block / mute / restrict — same shape, different paths
// Close friends, hashtag follows, muted terms — all under /actors/{actorId}/...

Notifications

// List
await agora.client.GET(
  "/v1/communities/{communityId}/actors/{actorId}/notifications",
  { ... },
);

// Unread count for the bell badge
await agora.client.GET(
  "/v1/communities/{communityId}/actors/{actorId}/notifications/unread-count",
  { ... },
);

// Mark all read
await agora.client.POST(
  "/v1/communities/{communityId}/actors/{actorId}/notifications/read-all",
  { ... },
);

Direct conversations

// Start / open a conversation
await agora.client.POST(
  "/v1/communities/{communityId}/conversations",
  { params: { ... }, body: { members: ["act_a", "act_b"] } },
);

// Send a message
await agora.client.POST(
  "/v1/communities/{communityId}/conversations/{conversationId}/messages",
  { ... },
);

Moderation (workspace admin)

// List flags
await agora.client.GET(
  "/v1/workspaces/{workspaceId}/communities/{communityId}/moderation/flags",
  { ... },
);

// Act on a flag (resolve / dismiss / take action on the target)
await agora.client.POST(
  "/v1/workspaces/{workspaceId}/communities/{communityId}/moderation/flags/{flagId}/actions",
  { params: { ... }, body: { action: "remove_content", note: "..." } },
);

// Shadow-ban an actor
await agora.client.PUT(
  "/v1/workspaces/{workspaceId}/communities/{communityId}/moderation/actors/{actorId}/shadow-ban",
  { ... },
);

How this SDK is built

Generated from the live OpenAPI spec at https://agora.productcraft.co/docs-json via openapi-typescript + openapi-fetch. The nightly spec-refresh workflow opens a PR whenever the spec changes.

License

MIT.