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

@wriven-ai/next

v0.2.2

Published

Next.js helpers for Wriven: a signed webhook → revalidate route handler and preview/draft wiring.

Readme

@wriven-ai/next

npm license

Next.js helpers for Wriven: a signature-verified webhook → ISR revalidation route handler, plus the raw signature verifier for custom handling.

  • Verified before anything runs — HMAC over the raw body, timing-safe compare, timestamp replay guard
  • Revalidate paths or tags per event — wire it to however you fetch
  • Zero config surface — one function, one export

Peer dependency: next >= 14 (App Router route handlers). next/cache is imported lazily, so this package never bundles Next itself.

npm i @wriven-ai/client @wriven-ai/next

A full Next.js setup uses all three Wriven packages: client fetches, react renders the body, next revalidates on publish.

Table of contents

Quickstart

Create an App Router route handler and re-export the generated POST:

// app/api/wriven/route.ts
import { createWebhookRoute } from '@wriven-ai/next';

export const { POST } = createWebhookRoute({
  secret: process.env.WRIVEN_WEBHOOK_SECRET!, // dashboard → Project Settings → Webhooks
  revalidate: (p) => ({
    paths: [`/blog/${p.entry.slug}`, '/blog'],
  }),
});

Then register a webhook in the dashboard pointing at https://yoursite.com/api/wriven. On every publish/unpublish/delete Wriven POSTs a signed payload; the route verifies it and revalidates the paths (or tags) you return for that event.

Events

| Event | Fires when | |-------|------------| | entry.published | an entry is published or re-published with changes | | entry.unpublished | a published entry goes back to draft | | entry.deleted | an entry is deleted |

Filter by event inside revalidate — return nothing (or {}) to skip:

revalidate: (p) =>
  p.event === 'entry.deleted'
    ? { paths: [`/blog/${p.entry.slug}`, '/blog'] }
    : { paths: ['/blog', `/blog/${p.entry.slug}`] },

Payload

revalidate and onEvent receive the verified body:

| Field | Type | Notes | |-------|------|-------| | event | 'entry.published' \| 'entry.unpublished' \| 'entry.deleted' | | | projectId | string | project the entry belongs to | | firedAt | string | ISO timestamp (also sent as X-Wriven-Timestamp) | | entry.id | string | | | entry.type | string | content type apiId, e.g. "blog_post" | | entry.slug | string | | | entry.status | string | draft / published / archived | | entry.publishedAt | string \| null | | | entry.updatedAt | string | ISO timestamp |

Options

createWebhookRoute({
  secret: string,             // required — the whsec_… signing secret
  revalidate?: (payload) => {
    paths?: (string | { path: string; type?: 'page' | 'layout' })[];
    tags?: string[];
  } | void,
  onEvent?: (payload) => void | Promise<void>,
})

| Option | Notes | |--------|-------| | secret | Shown exactly once when the webhook is created (dashboard → Project Settings → Webhooks). Keep it in a server env var. | | revalidate | Map an event to paths and/or tags to revalidate. Return nothing to skip. | | onEvent | Arbitrary side effect per verified event — logging, queueing a rebuild, analytics. Runs after revalidation; awaited. |

A plain string path is an exact URL. For a dynamic-segment pattern (all /blog/[slug] pages at once), wrap it and pass the route type — a bare '/blog/[slug]' string is treated as a literal URL and matches nothing:

revalidate: (p) => ({
  paths: ['/blog', { path: '/blog/[slug]', type: 'page' }],
})

Signature verification

Every Wriven webhook delivery carries:

  • X-Wriven-Signature: sha256=<hex> — HMAC-SHA256 of ${timestamp}.${rawBody} keyed with the webhook's signing secret
  • X-Wriven-Timestamp — ISO timestamp of the fire

verifyWrivenSignature (used internally, exported too) checks:

  1. both headers present,
  2. the timestamp is within ±5 minutes (replay guard — configurable via options.toleranceMs),
  3. the signature matches a timing-safe comparison over the raw body — never a re-serialized parse.

The route verifies before parsing or revalidating anything, so an unsigned/tampered request never triggers cache invalidation.

Tag-based revalidation

Paths are one option; tags scale better — for routes rendered on demand. Fetch with @wriven-ai/client using matching next.tags, then revalidate the tag for whole-type changes:

// When fetching (e.g. in a page or generateStaticParams)
const posts = await wriven.getEntries('blog_post', {
  next: { revalidate: 60, tags: ['type_blog_post'] },
});

// app/api/wriven/route.ts
export const { POST } = createWebhookRoute({
  secret: process.env.WRIVEN_WEBHOOK_SECRET!,
  revalidate: (p) => ({
    tags: ['type_blog_post'],          // every cached fetch with this tag is purged
    paths: [`/blog/${p.entry.slug}`],  // plus the affected page
  }),
});

Every cached fetch tagged type_blog_post is invalidated at once — no path list to maintain.

⚠️ Next.js 15/16 caveat — statically prerendered pages ignore tag purges. Build-time fetches for pages prerendered at build (fully static routes, no export const revalidate) are inlined into the prerender and never registered as tagged data-cache entries. revalidateTag for them is a silent no-op — the page stays stale forever. Two fixes, use both:

  1. Add export const revalidate = 300; (or similar) to every page that fetches Wriven content, so it is a real ISR route.
  2. Have revalidate return explicit paths for those pages (list the route per content type) — revalidatePath invalidates the full route cache regardless of tags:
const PATHS_BY_TYPE = {
  blog_post: ['/blog', { path: '/blog/[slug]', type: 'page' }],
  job_posting: ['/jobs'],
};
revalidate: (p) => ({
  paths: ['/', ...(PATHS_BY_TYPE[p.entry.type] ?? [])],
  tags: [`type_${p.entry.type}`],
})

Custom handling

Skip the route builder and verify yourself (works in any Node runtime — this function has no Next.js dependency):

import { verifyWrivenSignature } from '@wriven-ai/next';

export async function POST(req: Request) {
  const raw = await req.text();
  const headers = Object.fromEntries(req.headers); // keys are lowercase
  if (!verifyWrivenSignature(raw, headers, secret)) {
    return new Response('Bad signature', { status: 401 });
  }
  // …your logic
}

Tighten the replay window if your clocks are trusted:

verifyWrivenSignature(raw, headers, secret, { toleranceMs: 60_000 }); // ±1 min

Responses

| Status | Body | Meaning | |--------|------|---------| | 200 | { ok: true, event } | verified, revalidated, onEvent ran | | 401 | Invalid signature | missing/stale/tampered signature — not processed | | 400 | Invalid payload | body is not valid JSON |

Wriven retries failed deliveries (non-2xx) a few times with backoff; a 200 stops retries.

FAQ

Pages Router? The verifier works anywhere; createWebhookRoute targets App Router route.ts files (it returns a Request → Response handler). Use verifyWrivenSignature + res.revalidate() in Pages Router API routes.

Is next/cache bundled? No — it's dynamically imported at request time and marked external, so this package stays runtime- and version-agnostic (next >= 14).

Multiple webhooks / secrets? One route per secret, or read the signature yourself with verifyWrivenSignature and branch on X-Wriven-Event.

MIT