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

@dullaz/commerce

v0.1.1

Published

Ecommerce for EmDash: products, inventory, orders, checkout, and a pluggable payment-provider abstraction.

Readme

@dullaz/commerce

Ecommerce for EmDash: products, inventory, orders, checkout, and a pluggable payment-provider abstraction. Native-format plugin (runs in-process; ships a React admin UI).

What it does

  • Products are a normal EmDash content collection (so you get the full CMS editor, images, drafts, SEO, search). The plugin is configured to point at a collection — it does not own product schema.
  • Orders, inventory, carts, payments live in the plugin's own storage.
  • Payments go through a PaymentProvider interface. A working mock provider ships for development; rootline (staging) is scaffolded behind the same interface.

EmDash plugins cannot create or alter content-collection schema from their runtime context (no schema:write capability). The setup panel works around this by calling the official admin schema API as the logged-in admin.

Install

// astro.config.mjs
import { commercePlugin } from "@dullaz/commerce";

export default defineConfig({
  integrations: [
    emdash({
      database: d1({ binding: "DB" }),
      storage: r2({ binding: "MEDIA" }),
      plugins: [commercePlugin({ defaultCurrency: "USD" })],
    }),
  ],
});

Store setup (admin)

Open Store setup in the admin (/_emdash/admin/plugins/dullaz-commerce/setup):

  • Create a new collection — provisions a products collection with all required fields, or
  • Use an existing collection — pick one, validate the required fields, and one-click add any missing fields.

The chosen collection slug + field mapping are saved to plugin KV. If nothing is configured, the store falls back to a collection named products.

Required product fields

| Role | Default slug | Type | Notes | | ------------- | ------------- | -------------- | --------------------------------------- | | title | title | string | required | | price | price | integer | required — minor units (2500 = $25) | | currency | currency | string | ISO-4217; falls back to store default | | sku | sku | string | | | image | image | image | | | description | description | portableText | | | active | active | boolean | false hides from the store |

Payments

Set the provider under the plugin's settings (auto-generated form):

  • providermock (default) or rootline
  • rootlineBaseUrl, rootlineApiKey, rootlineWebhookSecret — rootline staging credentials (stored encrypted)

Adding a provider

Implement PaymentProvider (src/payments/provider.ts) and register it in src/payments/index.ts:

export interface PaymentProvider {
  id: string;
  label: string;
  createCheckout(args): Promise<{ checkoutId: string; redirectUrl: string }>;
  handleWebhook(args): Promise<{ orderId: string; outcome: "paid" | "failed" | "cancelled"; providerPaymentId?: string }>;
  refund(args): Promise<{ refunded: boolean; providerRefundId?: string }>;
}

createCheckout returns where to send the buyer; handleWebhook maps the provider's callback to an order outcome (the order id travels as the provider reference); refund reverses a captured payment. Read credentials from plugin settings via getPluginSetting() — never hardcode secrets.

rootline endpoints/auth/webhook signature are provisional (see src/payments/rootline.ts TODOs) and finalised once staging details land. Until configured, rootline fails fast with a clear "not configured" error.

Email

This plugin is an email consumer — it builds messages (order confirmations, magic links, verification, reset; see src/email/templates.ts) and sends them via ctx.email.send(). Delivery is handled by a separate transport plugin (@dullaz/email), which you configure and activate under the admin's Settings → Email. Commerce only declares the email:send capability and doesn't know or care which provider delivers.

Order-confirmation sends are best-effort: if no transport is active (or it errors), checkout still completes.

Order lifecycle

pending → awaiting_payment → paid → fulfilled
                 │             └→ refunded
                 ├→ cancelled
                 └→ expired

Stock is reserved at checkout, committed (decremented) on payment, and released on cancel/expiry. Untracked products sell without limit; enable tracking per product on the Inventory admin page.

Order history, accounts & refund requests

Email is required at checkout and stored (normalized, indexed) on the order.

  • Guest lookup/orders/lookup finds an order by its code + matching email (both required so a code alone never reveals an order).
  • Accounts — customers sign in with a password or a passwordless magic link, and see all orders for their email at /account. Email verification and password reset are emailed (need the email transport). Sessions are an httpOnly cookie backed by customer_sessions; passwords are PBKDF2 (src/auth.ts).
  • Refund requests — a customer flags an order from its detail view; the admin sees a "refund requested" badge on the Orders page and approves with the existing refund action.

Identity is the email; magic-link sign-in creates the account on first use.

API routes

Mounted at /_emdash/api/plugins/dullaz-commerce/<route>.

| Route | Method | Auth | Purpose | | ---------------- | ------ | ------ | -------------------------------- | | availability | GET | public | Live stock for product ids | | cart | GET | public | Read cart by ?token= | | cart/add | POST | public | Add a product | | cart/set | POST | public | Set/remove a line quantity | | cart/clear | POST | public | Empty the cart | | checkout | POST | public | Reserve + start provider checkout | | webhook | POST | public | Provider callback → order outcome | | order | GET | public | Sanitised order (success page) | | orders/lookup | POST | public | Guest lookup by code + email | | orders/request-refund | POST | public | Flag a refund request | | account/register | POST | public | Create a password account | | account/login | POST | public | Password sign-in | | account/logout | POST | public | End the session | | account/me | GET | public | Session's customer + orders | | account/magic | POST | public | Email a sign-in link | | account/consume| POST | public | Consume a magic/verify token | | account/reset-request | POST | public | Email a reset link | | account/reset | POST | public | Set a new password via token | | config | GET | admin | Effective store config | | config/save | POST | admin | Save collection + field map | | orders | GET | admin | List orders | | orders/refund | POST | admin | Refund + restock | | orders/fulfill | POST | admin | Mark fulfilled | | inventory | GET | admin | Products joined with stock | | inventory/set | POST | admin | Set tracking + on-hand | | stats | GET | admin | Dashboard metrics |

Development

bun test                            # domain + provider unit tests
bunx tsc -p tsconfig.json --noEmit

Money is always integer minor units. Domain logic (src/domain.ts) is pure and unit-tested; storage binding is in src/store.ts.