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

@keystrokehq/notion

v0.0.15

Published

Official Notion integration for Keystroke.

Readme

@keystrokehq/notion

Official Notion integration for Keystroke.

Import Paths

Use explicit subpath imports. The package root is intentionally non-canonical.

import { notion } from '@keystrokehq/notion/connection';
import { copyPageContent, createPage, getPage } from '@keystrokehq/notion/pages';
import { listBlockChildren } from '@keystrokehq/notion/blocks';
import { queryDataSource } from '@keystrokehq/notion/data-sources';
import { listComments, createComment } from '@keystrokehq/notion/comments';
import { getMe, listUsers } from '@keystrokehq/notion/users';
import { searchWorkspace } from '@keystrokehq/notion/search';
import { webhooks, polling } from '@keystrokehq/notion/triggers';

Connection Model

The public connection surface normalizes two auth paths into the same runtime credential shape:

  • OAuth connection through Keystroke-managed Notion public integrations
  • Manual internal-integration token entry

Both resolve to:

{
  NOTION_ACCESS_TOKEN: string;
}

In practice:

  • OAuth is the default path when users run keystroke connect notion
  • Manual token entry is for internal Notion integrations that already have a bearer token

Core Surfaces

  • pages: page reads, property reads, create, content copy, updates, trash, restore
  • blocks: block reads, child listing, append, update, delete
  • data-sources: canonical data-source discovery and query flows
  • comments: page comments and discussion replies
  • users: me, single-user lookup, workspace user listing
  • search: workspace-wide page and data-source discovery
  • triggers: webhook-first direct bindings plus polling parity helpers

data_source is the canonical runtime model in this package. Database discovery is still available for the upgrade path, but author-facing operations prefer data_source IDs over legacy database query endpoints.

For duplication-style workflows, pair createPage with copyPageContent. The helper recreates supported block trees level by level so it stays inside Notion's append limits. Pages containing API-unsupported block types such as child_page, child_database, or link_preview should use a template-driven flow instead.

Trigger Examples

import { webhooks, polling } from '@keystrokehq/notion/triggers';

const pageCreated = webhooks.pageCreated({
  name: 'Notion Page Created',
  transform: (event) => ({
    pageId: event.entityId,
    eventType: event.eventType,
  }),
});

const pageUpdated = polling.pageUpdated({
  name: 'Notion Page Updated',
  pageId: 'notion-page-id',
  schedule: '*/10 * * * *',
});

Webhook bindings emit metadata-first payloads. They intentionally do not auto-hydrate full Notion objects. Use the page, comment, or data-source operations to fetch current state when the workflow needs it.

Platform Admin Setup

Notion webhook subscriptions are configured in Notion, not through the public API. A Keystroke platform admin must complete the bootstrap:

  1. Create or update the Keystroke-managed Notion public integration in Notion.
  2. Enable the minimum capabilities the shipped surface expects:
    • Read content
    • Insert content
    • Update content
    • Read comment
    • Insert comment
    • User information
  3. Configure the OAuth redirect URL to Keystroke's /api/v1/connections/callback.
  4. Configure the webhook destination URL to Keystroke's /api/v1/webhooks/{orgId}/notion.
  5. Store the Notion OAuth app credentials in the internal notion-app credential set.
  6. Store the Notion webhook verification token in the internal notion-webhook credential set.

Provider Caveats

  • Notion OAuth is provider-specific. The Keystroke server uses a Notion-specific auth URL builder and token exchange flow instead of the generic OAuth strategy.
  • Notion webhook payloads are sparse. Event IDs, entity IDs, authors, and event metadata are the contract; workflows should fetch fresh objects when they need content.
  • Notion write operations are approval-gated by default.

Testing

  • Coverage map: See TEST_MATRIX.md for the full list of operations, webhook/polling triggers, and credential/server boundaries and what each must prove (success, failure, safety, live).

  • Unit / contract: pnpm test:unit — exercises .run() paths with mocked @notionhq/client, triggers, client helpers, events, verification, and schemas.

  • Live Notion (test:int): pnpm test:int — package-level integration suites in *.int.test.ts. Credentials follow a two-tier waterfall via the shared resolveLiveCredentialsForCredentialSet helper from @keystrokehq/test-utils:

    1. Env vars (CI-friendly): NOTION_TEST_ACCESS_TOKEN or NOTION_TEST_INTEGRATION_TOKEN in .env.test.
    2. Saved Keystroke connection (recommended for local dev): run pnpm cli auth + pnpm cli connect notion once. The suites then resolve credentials from the Keystroke server (POST /api/v1/credentials/resolve), so any OAuth refresh happens server-side and tests never touch the refresh token.

    Sandbox parent page is auto-discovered. The integration test harness calls Notion's search API in beforeAll to pick the first page the integration has access to as the sandbox parent. Just share at least one page with your Notion integration (during the OAuth consent, or later via the page share menu) and the page/block/comment/trigger suites will run end-to-end. Set NOTION_TEST_PARENT_PAGE_ID to pin a specific page (recommended in CI for deterministic runs).

    Other fixture IDs (NOTION_TEST_DATA_SOURCE_ID, NOTION_TEST_PAGE_ID, NOTION_TEST_PAGE_PROPERTY_ID, etc.) remain opt-in env overrides; tests that need them it.skipIf out cleanly when they are absent. Full list in the root .env.test.example. Without credentials, the suites skip the whole suite with a warning — no flag flipping required.

  • Server OAuth: Database-backed routes are covered under @keystroke/server (connections/.../initiate, connections/callback). Run turbo run test:int --filter=@keystroke/server with a local Postgres (see root .env.test.example).