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

@ezpost/sdk

v0.2.0

Published

Typed JavaScript/TypeScript client SDK for the EZPost External API

Readme

@ezpost/sdk

A typed TypeScript client SDK for the EZPost External API.

Installation

npm install @ezpost/sdk
# or
pnpm add @ezpost/sdk

Quick Start

import { EZPostClient } from '@ezpost/sdk';

const client = new EZPostClient({
  apiKey: 'your-api-key-here',
  baseUrl: 'https://api.yourdomain.com/api/v1/external', // optional
});

// List published posts
const { posts, count, totalPages } = await client.posts.list({
  isPublished: true,
  limit: 10,
  page: 1,
});

// Get a single post by slug
const { post, relatedPosts } = await client.posts.getBySlug('my-post-slug');

// List categories
const categories = await client.categories.list();

Configuration

| Option | Type | Required | Default | Description | |--------|------|----------|---------|-------------| | apiKey | string | Yes | — | Your EZPost API key | | baseUrl | string | No | https://api.yourdomain.com/api/v1/external | API base URL | | timeout | number | No | 10000 (10s) | Request timeout in milliseconds | | headers | Record<string, string> | No | {} | Custom headers sent with every request |

API Reference

client.posts.list(query?)

Retrieves a paginated list of posts.

| Parameter | Type | Default | Description | |-----------|------|---------|-------------| | limit | number | 10 | Posts per page | | page | number | 1 | Page number | | isPublished | boolean | — | Filter by published status | | isFeatured | boolean | — | Filter by featured status | | categoryId | string | — | Comma-separated category IDs | | search | string | — | Search in title and description | | sortBy | 'createdAt' \| 'updatedAt' \| 'totalViews' | 'createdAt' | Sort field | | sortOrder | 'asc' \| 'desc' | 'desc' | Sort direction |

const response = await client.posts.list({
  isPublished: true,
  sortBy: 'totalViews',
  sortOrder: 'desc',
});
// response.posts, response.count, response.totalPages, response.hasMore, response.hasLess

client.posts.listAll(query?)

Async iterator that automatically paginates through all matching posts.

for await (const post of client.posts.listAll({ isPublished: true })) {
  console.log(post.title);
}

client.posts.getBySlug(slug)

Retrieves a single published post by its slug, including related posts.

const { post, relatedPosts } = await client.posts.getBySlug('my-post');

client.posts.incrementViews(slug)

Increments the view count for a post.

await client.posts.incrementViews('my-post');

client.categories.list()

Lists all categories with their subcategories.

const categories = await client.categories.list();
// categories[0].name, categories[0].subcategories[0].name

Content Utilities

Posts support two content formats: Markdown and BlockNote rich text. The isMarkdownFormat flag indicates which format a post uses. These utilities help you handle both formats.

extractPlainText(post)

Returns plain text regardless of content format. For markdown posts, returns the markdown string. For BlockNote posts, joins block content with newlines.

import { extractPlainText } from '@ezpost/sdk';

const post = await client.posts.getBySlug('my-post');
const text = extractPlainText(post.post);

parseContent(post)

Returns the parsed content. For markdown posts, returns a string. For BlockNote posts, returns an array of block objects.

import { parseContent } from '@ezpost/sdk';

const post = await client.posts.getBySlug('my-post');
const content = parseContent(post.post);

if (typeof content === 'string') {
  // Render as markdown
} else {
  // Render as BlockNote blocks
}

isMarkdown(post)

Returns true if the post uses markdown format, false for BlockNote.

import { isMarkdown } from '@ezpost/sdk';

if (isMarkdown(post.post)) {
  // Use markdown renderer
} else {
  // Use BlockNote renderer
}

extractTableOfContents(post)

Extracts a table of contents (heading outline) from post content, regardless of format. Returns an array of TocItem objects with id, level, and text.

For markdown posts, parses #-###### heading syntax. For BlockNote posts, filters heading blocks and extracts their text content.

import { extractTableOfContents } from '@ezpost/sdk';

const { post } = await client.posts.getBySlug('my-post');
const toc = extractTableOfContents(post);

// [
//   { id: 'introduction', level: 2, text: 'Introduction' },
//   { id: 'getting-started', level: 3, text: 'Getting Started' },
//   { id: 'conclusion', level: 2, text: 'Conclusion' },
// ]

toc.forEach((item) => {
  console.log(`${'  '.repeat(item.level - 1)}${item.text} → #${item.id}`);
});

Type:

type TocItem = {
  readonly id: string;    // slug or block id — safe for anchor links
  readonly level: number; // 1–6
  readonly text: string;  // heading text
};

Error Handling

All errors are thrown as EZPostError instances with status, code, message, and details properties.

import { EZPostClient, EZPostError } from '@ezpost/sdk';

try {
  const post = await client.posts.getBySlug('nonexistent');
} catch (error) {
  if (error instanceof EZPostError) {
    console.log(error.status);  // 404
    console.log(error.code);    // "Post not found"
    console.log(error.message); // "Post not found"
    console.log(error.details); // response body
  }
}

TypeScript

All types are inferred from Zod schemas and exported from the package. You can import them directly:

import type { Post, PostsResponse, Category, ListPostsQuery } from '@ezpost/sdk';

ESM Only

This package is ESM-only. It requires Node.js 18+ or a modern bundler that supports ES modules. If you're using CommonJS, use dynamic import:

const { EZPostClient } = await import('@ezpost/sdk');

React Hooks

React hooks wrapping TanStack Query are available as a separate sub-export at @ezpost/sdk/react.

Installation

npm install @ezpost/sdk @tanstack/react-query react

Provider Setup

Wrap your app with EZPostClientProvider:

import { EZPostClient, EZPostClientProvider } from '@ezpost/sdk';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const client = new EZPostClient({ apiKey: 'your-api-key' });
const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <EZPostClientProvider client={client}>
        <YourApp />
      </EZPostClientProvider>
    </QueryClientProvider>
  );
}

usePosts

import { usePosts } from '@ezpost/sdk/react';

function PostList() {
  const { data, isLoading } = usePosts({ isPublished: true });
  if (isLoading) return <p>Loading...</p>;
  return (
    <ul>
      {data.posts.map((post) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

usePost

import { usePost } from '@ezpost/sdk/react';

function PostDetail({ slug }: { slug: string }) {
  const { data, isLoading } = usePost(slug);
  if (isLoading) return <p>Loading...</p>;
  return <h1>{data.post.title}</h1>;
}

useCategories

import { useCategories } from '@ezpost/sdk/react';

function CategoryList() {
  const { data, isLoading } = useCategories();
  if (isLoading) return <p>Loading...</p>;
  return (
    <ul>
      {data.map((cat) => (
        <li key={cat.id}>{cat.name}</li>
      ))}
    </ul>
  );
}

usePostTableOfContents

Memoized hook that extracts a table of contents from a post object. Works with both markdown and BlockNote formats. Returns an empty array on parse errors.

import { usePostTableOfContents } from '@ezpost/sdk/react';

function TableOfContents({ post }: { post: Post }) {
  const toc = usePostTableOfContents(post);
  if (toc.length === 0) return null;
  return (
    <nav>
      {toc.map((item) => (
        <a
          key={item.id}
          href={`#${item.id}`}
          style={{ paddingLeft: `${(item.level - 1) * 12}px` }}
        >
          {item.text}
        </a>
      ))}
    </nav>
  );
}

License

MIT