CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-notionhq--client

A simple and easy to use client for the Notion API

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

pagination-helpers.mddocs/

Pagination Helpers

Utility functions for handling paginated API responses with automatic cursor management.

Capabilities

Iterate Paginated API

Create an async iterator to automatically handle pagination for any paginated Notion API endpoint.

/**
 * Returns an async iterator over the results of any paginated Notion API
 * @param listFn - A bound function on the Notion client that represents a paginated API
 * @param firstPageArgs - Arguments for the first and subsequent API calls
 * @returns Async iterator yielding individual results
 */
function iteratePaginatedAPI<T>(
  listFn: (args: any) => Promise<PaginatedList<T>>,
  firstPageArgs: any
): AsyncIterableIterator<T>;

interface PaginatedList<T> {
  object: "list";
  results: T[];
  next_cursor: string | null;
  has_more: boolean;
}

Usage Examples:

import { iteratePaginatedAPI } from "@notionhq/client";

// Iterate over all block children
for await (const block of iteratePaginatedAPI(notion.blocks.children.list, {
  block_id: "parent-block-id",
})) {
  console.log(`Block type: ${block.type}`);
  // Process each block individually
}

// Iterate over all database pages
for await (const page of iteratePaginatedAPI(notion.dataSources.query, {
  data_source_id: "database-id",
  filter: {
    property: "Status",
    select: { equals: "Published" },
  },
})) {
  console.log(`Page: ${page.properties.title?.title?.[0]?.text?.content}`);
}

// Iterate over all users
for await (const user of iteratePaginatedAPI(notion.users.list, {})) {
  console.log(`User: ${user.name} (${user.type})`);
}

// Iterate with processing
const processedBlocks = [];
for await (const block of iteratePaginatedAPI(notion.blocks.children.list, {
  block_id: "page-id",
})) {
  if (block.type === "paragraph") {
    processedBlocks.push({
      id: block.id,
      content: block.paragraph.rich_text[0]?.text?.content || "",
    });
  }
}

Collect Paginated API

Collect all results from a paginated API into a single array.

/**
 * Collects all results from a paginated API into an array
 * @param listFn - A bound function on the Notion client that represents a paginated API
 * @param firstPageArgs - Arguments for the first and subsequent API calls
 * @returns Promise resolving to array of all results
 */
function collectPaginatedAPI<T>(
  listFn: (args: any) => Promise<PaginatedList<T>>,
  firstPageArgs: any
): Promise<T[]>;

Usage Examples:

import { collectPaginatedAPI } from "@notionhq/client";

// Get all block children at once
const allBlocks = await collectPaginatedAPI(notion.blocks.children.list, {
  block_id: "parent-block-id",
});

console.log(`Total blocks: ${allBlocks.length}`);
allBlocks.forEach(block => {
  console.log(`- ${block.type}`);
});

// Get all pages from a database
const allPages = await collectPaginatedAPI(notion.dataSources.query, {
  data_source_id: "database-id",
  sorts: [
    {
      property: "Created",
      direction: "descending",
    },
  ],
});

// Get all users in workspace
const allUsers = await collectPaginatedAPI(notion.users.list, {});

// Get all comments on a page
const allComments = await collectPaginatedAPI(notion.comments.list, {
  block_id: "page-id",
});

// Collect with filters
const completedTasks = await collectPaginatedAPI(notion.dataSources.query, {
  data_source_id: "tasks-database-id",
  filter: {
    property: "Status",
    select: { equals: "Complete" },
  },
});

Comparison: Manual vs Helper Functions

Manual Pagination

// Manual pagination - more control but more code
async function getAllPagesManually(databaseId: string) {
  let cursor: string | undefined;
  let allPages: PageObjectResponse[] = [];
  
  do {
    const response = await notion.dataSources.query({
      data_source_id: databaseId,
      start_cursor: cursor,
      page_size: 100,
    });
    
    allPages.push(...response.results);
    cursor = response.next_cursor || undefined;
  } while (cursor);
  
  return allPages;
}

Using Helper Functions

// Using helper functions - cleaner and less error-prone
const allPages = await collectPaginatedAPI(notion.dataSources.query, {
  data_source_id: databaseId,
});

// Or with async iteration for processing as you go
for await (const page of iteratePaginatedAPI(notion.dataSources.query, {
  data_source_id: databaseId,
})) {
  // Process page immediately without storing all in memory
  await processPage(page);
}

Advanced Usage Patterns

Memory-Efficient Processing

// Process large datasets without loading everything into memory
async function processLargeDatabase(databaseId: string) {
  let processedCount = 0;
  
  for await (const page of iteratePaginatedAPI(notion.dataSources.query, {
    data_source_id: databaseId,
  })) {
    // Process each page individually
    await performExpensiveOperation(page);
    
    processedCount++;
    if (processedCount % 100 === 0) {
      console.log(`Processed ${processedCount} pages...`);
    }
  }
  
  return processedCount;
}

Filtered Collection

// Collect only specific items
async function getRecentPages(databaseId: string, daysBack: number = 7) {
  const cutoffDate = new Date();
  cutoffDate.setDate(cutoffDate.getDate() - daysBack);
  
  const recentPages = [];
  
  for await (const page of iteratePaginatedAPI(notion.dataSources.query, {
    data_source_id: databaseId,
    sorts: [{ property: "Created", direction: "descending" }],
  })) {
    const createdDate = new Date(page.created_time);
    
    if (createdDate >= cutoffDate) {
      recentPages.push(page);
    } else {
      // Since we're sorting by created date descending,
      // we can break early once we hit older pages
      break;
    }
  }
  
  return recentPages;
}

Parallel Processing with Batching

// Process pages in parallel batches
async function processInBatches(databaseId: string, batchSize: number = 10) {
  const pages = await collectPaginatedAPI(notion.dataSources.query, {
    data_source_id: databaseId,
  });
  
  // Process in batches
  for (let i = 0; i < pages.length; i += batchSize) {
    const batch = pages.slice(i, i + batchSize);
    
    // Process batch in parallel
    await Promise.all(batch.map(page => processPage(page)));
    
    console.log(`Processed batch ${Math.floor(i / batchSize) + 1}`);
  }
}

Type Guards for Paginated Results

Helper functions work with Notion's type guards for safer processing:

import { 
  collectPaginatedAPI, 
  iteratePaginatedAPI,
  isFullPage,
  isFullBlock 
} from "@notionhq/client";

// Safely process search results
const searchResults = await collectPaginatedAPI(notion.search, {
  query: "project",
});

const pages = searchResults.filter(isFullPage);
const databases = searchResults.filter(result => 
  result.object === "database"
);

// Process blocks with type safety
for await (const block of iteratePaginatedAPI(notion.blocks.children.list, {
  block_id: "page-id",
})) {
  if (isFullBlock(block) && block.type === "paragraph") {
    console.log(block.paragraph.rich_text[0]?.text?.content);
  }
}

Rich Text Type Guards

Additional helper functions for type-safe handling of RichText items.

Text Rich Text Type Guard

Checks if a rich text item is a text type.

/**
 * Type guard to check if richText is a TextRichTextItemResponse
 * @param richText - Rich text item to check
 * @returns true if richText is a text type
 */
function isTextRichTextItemResponse(
  richText: RichTextItemResponse
): richText is RichTextItemResponseCommon & TextRichTextItemResponse;

Equation Rich Text Type Guard

Checks if a rich text item is an equation type.

/**
 * Type guard to check if richText is an EquationRichTextItemResponse
 * @param richText - Rich text item to check
 * @returns true if richText is an equation type
 */
function isEquationRichTextItemResponse(
  richText: RichTextItemResponse
): richText is RichTextItemResponseCommon & EquationRichTextItemResponse;

Mention Rich Text Type Guard

Checks if a rich text item is a mention type.

/**
 * Type guard to check if richText is a MentionRichTextItemResponse
 * @param richText - Rich text item to check
 * @returns true if richText is a mention type
 */
function isMentionRichTextItemResponse(
  richText: RichTextItemResponse
): richText is RichTextItemResponseCommon & MentionRichTextItemResponse;

Usage Examples:

import { 
  isTextRichTextItemResponse,
  isEquationRichTextItemResponse,
  isMentionRichTextItemResponse 
} from "@notionhq/client";

// Process rich text content with type safety
function processRichText(richTextArray: RichTextItemResponse[]) {
  richTextArray.forEach(richTextItem => {
    if (isTextRichTextItemResponse(richTextItem)) {
      // Safe to access text-specific properties
      console.log(`Text: ${richTextItem.text.content}`);
      console.log(`Bold: ${richTextItem.annotations.bold}`);
      console.log(`Link: ${richTextItem.text.link?.url || 'No link'}`);
    } else if (isEquationRichTextItemResponse(richTextItem)) {
      // Safe to access equation-specific properties
      console.log(`Equation: ${richTextItem.equation.expression}`);
    } else if (isMentionRichTextItemResponse(richTextItem)) {
      // Safe to access mention-specific properties
      console.log(`Mention type: ${richTextItem.mention.type}`);
      if (richTextItem.mention.type === 'user') {
        console.log(`User ID: ${richTextItem.mention.user.id}`);
      }
    }
  });
}

// Extract plain text from rich text array
function extractPlainText(richTextArray: RichTextItemResponse[]): string {
  return richTextArray
    .filter(isTextRichTextItemResponse)
    .map(item => item.text.content)
    .join('');
}

// Find all mentions in rich text
function findMentions(richTextArray: RichTextItemResponse[]) {
  return richTextArray.filter(isMentionRichTextItemResponse);
}

Types

interface PaginatedArgs {
  start_cursor?: string;
}

interface PaginatedList<T> {
  object: "list";
  results: T[];
  next_cursor: string | null;
  has_more: boolean;
}

/**
 * Returns an async iterator over the results of any paginated Notion API
 */
function iteratePaginatedAPI<T>(
  listFn: (args: PaginatedArgs & any) => Promise<PaginatedList<T>>,
  firstPageArgs: any
): AsyncIterableIterator<T>;

/**
 * Collects all results from a paginated API into an array
 */
function collectPaginatedAPI<T>(
  listFn: (args: PaginatedArgs & any) => Promise<PaginatedList<T>>,
  firstPageArgs: any
): Promise<T[]>;

// Rich Text Types
type RichTextItemResponse = TextRichTextItemResponse | MentionRichTextItemResponse | EquationRichTextItemResponse;

interface RichTextItemResponseCommon {
  type: string;
  annotations: {
    bold: boolean;
    italic: boolean;
    strikethrough: boolean;
    underline: boolean;
    code: boolean;
    color: string;
  };
  plain_text: string;
  href: string | null;
}

interface TextRichTextItemResponse {
  type: "text";
  text: {
    content: string;
    link: { url: string } | null;
  };
}

interface EquationRichTextItemResponse {
  type: "equation";
  equation: {
    expression: string;
  };
}

interface MentionRichTextItemResponse {
  type: "mention";
  mention: {
    type: "user" | "page" | "database" | "date" | "link_preview" | "template_mention";
    user?: { id: string };
    page?: { id: string };
    database?: { id: string };
    date?: { start: string; end?: string; time_zone?: string };
    link_preview?: { url: string };
    template_mention?: { type: string; template_mention_date?: string; template_mention_user?: string };
  };
}

Install with Tessl CLI

npx tessl i tessl/npm-notionhq--client

docs

block-operations.md

client-configuration.md

comments.md

data-source-operations.md

database-operations.md

error-handling.md

file-uploads.md

index.md

oauth-authentication.md

page-operations.md

pagination-helpers.md

search.md

user-management.md

tile.json