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

@ledewire/node

v0.9.0

Published

LedeWire SDK for Node.js — full API surface for building merchant stores

Readme

@ledewire/node

npm License: MIT

Node.js SDK for the LedeWire content marketplace — full API surface for building merchant stores, managing sellers, and processing buyer flows on the server side.

Install

npm install @ledewire/node

Quick Start

import { createClient } from '@ledewire/node'

// Full access: API key + secret grants read/write seller permissions
const client = createClient({
  apiKey: process.env.LEDEWIRE_API_KEY,
  apiSecret: process.env.LEDEWIRE_API_SECRET,
})

// Merchant email/password auth
const client = createClient()
await client.merchant.auth.loginWithEmail({ email, password })
const stores = await client.merchant.auth.listStores()

Client Namespaces

| Namespace | Description | | ------------------------------ | ---------------------------------------------------------------- | | client.config | Platform-level public config (no auth required) | | client.auth | Buyer signup, email/password login, Google OAuth, password reset | | client.wallet | Buyer wallet balance and payment sessions | | client.purchases | Buyer purchase history, create purchases, verify ownership | | client.content | Fetch content with buyer access info | | client.checkout | Checkout state — what action is required next | | client.user.apiKeys | Manage buyer API keys for autonomous agents | | client.merchant.auth | Merchant login (email / Google), store discovery, password reset | | client.merchant.users | Merchant user management (invite, list, update, remove) | | client.merchant.content | Merchant content CRUD + search (merchant JWT auth) | | client.merchant.buyers | Buyer statistics within a store | | client.merchant.sales | Sales reporting and revenue statistics | | client.merchant.config | Store configuration | | client.merchant.domains | x402 domain verification for URL-based content gating | | client.merchant.pricingRules | x402 URL pattern-based pricing rules | | client.seller.content | Seller content CRUD + search (API key auth) | | client.seller.sales | Seller sales statistics and revenue reporting | | client.seller.buyers | Anonymized buyer statistics (API key auth) | | client.seller.config | Store configuration (API key auth) |

Configuration

const client = createClient({
  apiKey: process.env.LEDEWIRE_API_KEY,
  apiSecret: process.env.LEDEWIRE_API_SECRET, // omit for read-only seller access
  baseUrl: 'https://api-staging.ledewire.com', // optional, defaults to production

  // Persist tokens across server restarts (optional)
  storage: {
    getTokens: async () => JSON.parse((await redis.get('lw:tokens')) ?? 'null'),
    setTokens: async (t) => redis.set('lw:tokens', JSON.stringify(t)),
    clearTokens: async () => redis.del('lw:tokens'),
  },

  // Side-effects only — storage.setTokens is already the persistence hook.
  // Use onTokenRefreshed for audit logging or cache invalidation on refresh.
  onTokenRefreshed: async (tokens) => {
    await auditLog.record('token_refreshed', { expiresAt: tokens.expiresAt })
  },

  onAuthExpired: () => {
    console.error('LedeWire session expired — re-authenticate')
  },
})

Token refresh is handled automatically — you never need to call a refresh method manually.

Serverless / edge note: The default MemoryTokenStorage resets on every cold start, which means tokens are lost between function invocations. Always provide a custom storage adapter (database, Redis, encrypted cookie) when deploying to serverless or edge runtimes.

Example: Merchant JWT Auth (no API key)

Use this flow when running a merchant backend that authenticates via email/password or Google. No API key is required. Token refresh is automatic — the SDK handles it transparently.

Email / password login

The one-step helper logs in and returns both the normalized tokens and the accessible stores list in a single HTTP call:

import { createClient, ForbiddenError } from '@ledewire/node'

const client = createClient({
  // Required for serverless/edge — MemoryTokenStorage (default) resets on cold start.
  storage: {
    getTokens: async () => JSON.parse((await redis.get('lw:tokens')) ?? 'null'),
    setTokens: async (t) => redis.set('lw:tokens', JSON.stringify(t)),
    clearTokens: async () => redis.del('lw:tokens'),
  },
  onAuthExpired: () => redirect('/login'),
})

try {
  const { tokens, stores } = await client.merchant.auth.loginWithEmailAndListStores({
    email: '[email protected]',
    password: process.env.MERCHANT_PASSWORD,
  })
  // tokens: StoredTokens — { accessToken, refreshToken, expiresAt: number (Unix ms) }
  // stores: MerchantLoginStore[] — use .id, .name, .role
  const storeId = stores[0].id
} catch (err) {
  if (err instanceof ForbiddenError) {
    // Valid credentials but account has no merchant store access (e.g. buyer account).
    // err.message: "This account does not have merchant access. Use a merchant or owner account."
  }
}

Google OAuth login

Same flow with a Google ID token instead of email/password:

const { tokens, stores } = await client.merchant.auth.loginWithGoogleAndListStores({
  id_token: googleIdToken, // from Google Identity Services callback
})
const storeId = stores[0].id

Password reset

Two-step flow — request a code, then submit it with the new password:

// Step 1 — send a 6-digit reset code to the merchant's email.
// Always returns 200 to prevent email enumeration.
await client.merchant.auth.requestPasswordReset({ email: '[email protected]' })

// Step 2 — submit the code and new password.
await client.merchant.auth.resetPassword({
  email: '[email protected]',
  reset_code: '246810',
  password: 'new-secure-password',
})

Separate login + store list (when you need full store detail)

Use this only when you need fields available on ManageableStore but not on MerchantLoginStore (store_key, logo):

await client.merchant.auth.loginWithEmail({ email, password })
const stores = await client.merchant.auth.listStores() // ManageableStore[]
const storeId = stores[0].id // .id and .name match MerchantLoginStore

Example: Merchant Store Setup

const client = createClient()

await client.merchant.auth.loginWithEmail({
  email: '[email protected]',
  password: process.env.MERCHANT_PASSWORD,
})

const stores = await client.merchant.auth.listStores()
const storeId = stores[0].id

// Create a markdown article
await client.seller.content.create(storeId, {
  content_type: 'markdown',
  title: 'Hello World',
  content_body: btoa('# Hello World\nFull article body here.'),
  price_cents: 500,
  visibility: 'public',
})

// Create an external reference (e.g. a Vimeo video)
await client.seller.content.create(storeId, {
  content_type: 'external_ref',
  title: 'Intro to Machine Learning',
  content_uri: 'https://vimeo.com/987654321',
  external_identifier: 'vimeo:987654321',
  price_cents: 1500,
  visibility: 'public',
})

const items = await client.seller.content.list(storeId)
// items.data — ContentListItem[]
// items.pagination — PaginationMeta

// Search by title (partial match), URI, and/or metadata
const results = await client.seller.content.search(storeId, { title: 'intro' })
const byUri = await client.seller.content.search(storeId, { uri: 'vimeo.com' })
const combined = await client.seller.content.search(storeId, {
  title: 'tutorial',
  metadata: { category: 'ml' },
})

// Fetch Google OAuth client ID before the user has signed in
const { google_client_id } = await client.config.getPublic()
// google.accounts.id.initialize({ client_id: google_client_id, callback })

Error Handling

All SDK errors extend LedewireError — use instanceof checks on named subclasses:

import { createClient, ForbiddenError, AuthError, NotFoundError } from '@ledewire/node'

try {
  await client.merchant.auth.loginWithGoogle({ id_token })
} catch (err) {
  if (err instanceof ForbiddenError) {
    // 403 — credentials are VALID but the account has no merchant store access.
    // This is the expected error when a personal Google account previously
    // registered as a buyer is used on the merchant login endpoint.
    // err.message → "This account does not have merchant access. Use a merchant or owner account."
    // Fix: use a dedicated merchant/owner account, or have a store owner add your account.
    console.error('Wrong account role:', err.message)
  } else if (err instanceof AuthError) {
    // 401 — bad credentials or expired token. Re-authenticate.
    console.error('Authentication failed:', err.message)
  } else if (err instanceof NotFoundError) {
    // 404 — resource not found (e.g. wrong email/password on email login).
    console.error('Not found:', err.message)
  }
}

| Subclass | Status | When thrown | | ---------------- | ------- | ----------------------------------------------------------------------- | | AuthError | 401 | Invalid credentials, expired token, failed token refresh | | ForbiddenError | 403 | Valid credentials, wrong account role (e.g. buyer on merchant endpoint) | | NotFoundError | 404 | Resource not found, wrong email/password on email login | | PurchaseError | 409/422 | Purchase validation failure (price mismatch, duplicate, etc.) | | LedewireError | any | Catch-all base class for all other API errors |

Documentation

License

MIT