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

@jitl/notion-api

v0.2.2

Published

The missing companion library for the official Notion public API.

Downloads

201

Readme

@jitl/notion-api

The missing companion library for the official Notion public API.

  • Use Notion as a headless content management system a la Contentful.
  • Recursively fetch page content while building backlinks.
  • Convenient types like Page Block ..., plus helpers for tasks like iterating paginated API results.
  • Image, emoji, and content caching specifically designed for NextJS and incremental static regeneration.

This is not an official Notion product. The current focus of this library is on reading data from Notion.

Github | Full API documentation | NPM Package

CMS

The CMS class is a wrapper around a Notion database. A CMS instance adds the following features:

  • Page content fetching and caching. Calling CMS methods to retrieve pages from the Notion API will only re-fetch the contents of the page if the page has been updated. Cached page content can optionally be persisted to disk as JSON files.
  • Optional cover image, icon and image block asset download, including images for unicode emojis.
  • Automatically derive a metadata object called frontmatter for each page, to reduce page property parsing boilerplate, and provide a type-safe API for your pages to the rest of your app.
  • Support for retrieving pages by a special slug property suitable for use in a URL.
import {
  NotionClient, // re-exported official Notion client from peer dependencies
  NotionClientDebugLogger, // enable logs with DEBUG='@jitl/notion-api:*'
  CMS,
  richTextAsPlainText,
} from '@jitl/notion-api';

const Recipes = new CMS({
  database_id: 'a3aa29a6b2f242d1b4cf86fb578a5eea',
  notion: new NotionClient({
    logger: NotionClientDebugLogger,
    auth: process.env.NOTION_SECRET,
  }),
  slug: undefined, // Use page ID
  visible: true, // All pages visible
  getFrontmatter: (page) => ({
    /* TODO: return your custom metadata */
  }),
  cache: {
    directory: path.join(__dirname, './cache'),
  },
  assets: {
    directory: path.join(__dirname, './assets'),
    downloadExternalAssets: true,
  },
});

// Download and cache all pages in the Recipes database, and their assets.
for await (const recipe of Recipes.query()) {
  console.log(
    'Downloading assets for recipe: ',
    richTextAsPlainText(recipe.frontmatter.title)
  );
  await Recipes.downloadAssets(recipe);
}

API Types & Helpers

This library exports many type aliases for working with data retrieved from the official @notionhq/client library.

These types are derived from the official library's publicly exported types. They will be compatible with @notionhq/client, but may change in unexpected ways after a @notionhq/client update.

Abbreviated list of types: Block<BlockType>, Page, RichText, RichTextToken, Mention<MentionType>, Property, PropertyFilter, User, etc.

There are several handy utility functions for working with those types, like richTextAsPlainText(text) and getPropertyValue(page, propertyPointer).

See the full list in the API documentation.

iteratePaginatedAPI

Dealing with pagination is annoying, but necessary to avoid resource consumption.

The iteratePaginatedAPI helper returns an AsyncIterable<Item> so you can iterate over Notion API results using the for await (...) { ... } syntax. This should work for any paginated API using Notion's official API client.

for await (const block of iteratePaginatedAPI(notion.blocks.children.list, {
  block_id: parentBlockId,
})) {
  // Do something with block.
}

If you prefer a function approach and don't mind waiting for all values to load into memory, consider asyncIterableToArray:

const iterator = iteratePaginatedAPI(notion.blocks.children.list, {
  block_id: parentBlockId,
});
const blocks = await asyncIterableToArray(iterator);
const paragraphs = blocks.filter((block) => isFullBlock(block, 'paragraph'));

Partial response types

The Notion API can sometimes return "partial" object data that contain only the block's ID:

// In @notionhq/client typings:
type PartialBlockObjectResponse = { object: 'block'; id: string };
export type GetBlockResponse = PartialBlockObjectResponse | BlockObjectResponse;

Checking that a GetBlockResponse (or similar type) is a "full" block gets old pretty fast, so this library exports type guard functions to handle common cases, like isFullPage(page) and isFullBlock(block).

isFullBlock can optionally narrow the type of block as well:

if (isFullBlock(block, 'paragraph')) {
  // It's a full paragraph block
  console.log(richTextAsPlainText(block.paragraph.text));
}

Block data

Notion's API returns block data in a shape that is very difficult to deal with in a generic way while maintaining type-safety. Each block type has it's own property with the same name, and that property contains the block's data. Handling this type-safely means writing a long and annoying switch statement:

function getBlockTextContentBefore(block: Block): RichText | RichText[] {
  switch (block.type) {
    case 'paragraph':
      return block.paragraph.rich_text;
    case 'heading_1':
      return block.heading_1.rich_text;
    case 'heading_2':
      return block.heading_2.rich_text;
    // ... etc, for many more block types
    default:
      assertUnreachable(block); // Assert this switch is exhaustive
  }
}

Enter getBlockData. It returns a union of all possible interior data types for a block value. The same function can be re-written in a type-safe but non-exhaustive way in much fewer lines:

function getBlockTextContentAfter(block: Block): RichText[] {
  const blockData = getBlockData(block);
  const results: RichText[] = [];
  if ('rich_text' in blockData) {
    results.push(blockData.rich_text);
  }
  if ('caption' in blockData) {
    results.push(blockData.caption);
  }
  // Done.
  return results;
}

But because this function supports narrowed block types, you can still use a switch (block.type) if you want to be exhaustive, and tab completion will guide you:

function getBlockTextContentAfterExhaustive(
  block: Block
): RichText | RichText[] {
  switch (block.type) {
    case 'paragraph': // Fall-through for blocks with only rich_text
    case 'heading_1':
    case 'heading_2': // ... etc
      return getBlockData(block).rich_text;
    case 'image':
      return getBlockData(block).caption;
    case 'code':
      return [getBlockData(block).rich_text, getBlockData(block).caption];
    // ... etc
    default:
      assertUnreachable(block); // Assert this switch is exhaustive
  }
}

See the full list of functions in the API documentation.

Stability & Support

API stability: This library follows SemVer, and currently has a version less that 1.0.0, meaning it is under initial development. Do not expect API stability between versions, so specify an exact version in package.json or use a lockfile (package-lock.json, yarn.loc etc) to protect yourself from unexpected breaking changes.

Support: As stated above, this library not an official Notion product. I wrote it for my own use, to support my website and other projects, although I welcome contributions of any kind. There are no automated tests yet.

TypeScript: This library is developed with TypeScript 4.5.5, and is untested with other TypeScript versions.

Development

Monorepo

This library is developed inside a monorepo, please see the root README.md for more information.

Running unit tests

Run nx test notion-api to execute the unit tests via Jest.