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

@palinde/sdk

v1.1.2

Published

Official Palinde JavaScript SDK for headless storefronts (Framer, Webflow, React, anywhere JS runs).

Readme

@palinde/sdk

Official JavaScript SDK for the Palinde public commerce API. Build a complete headless storefront for any Palinde merchant — from any framework, anywhere JavaScript runs.

npm install @palinde/sdk

No build step? Use the CDN:

<script type="module">
	import { PalindeSDK } from "https://cdn.jsdelivr.net/npm/@palinde/sdk@1/dist/index.mjs";
</script>

What it does

Every endpoint of the Palinde public API is exposed as a typed method. The cart token persists automatically in localStorage, so users don't lose their cart between page loads. Variants, promo codes, fees, taxes, delivery options — all handled. Payment goes through Paystack; you just redirect.

60-second example

import { PalindeSDK } from "@palinde/sdk";

const palinde = new PalindeSDK({ key: "pk_live_..." });

// 1. Show the store (read currency, branches, fee tiers)
const store = await palinde.store.get();
const branchId = store.branches.find((b) => b.isDefault)?._id;

// 2. List products (pass branchId for branch-scoped stock)
const { items: products } = await palinde.products.list({ page: 1, branchId });

// 3. Pick a variant if the product has them
const product = await palinde.products.get(products[0].sku, { branchId });
const skuToBuy = product.hasVariants ? product.variants[0].sku : product.sku;

// 4. Add to cart (cart auto-created + persisted)
await palinde.carts.ensure({ branchId });
await palinde.carts.addItem({ sku: skuToBuy, quantity: 2 });

// 5. Preview totals
const quote = await palinde.carts.quote();
console.log(`Total: ${quote.currency} ${quote.total}`);

// 6. Checkout → redirect to Paystack
const { paymentUrl } = await palinde.checkout.create({
	customer: { phone: "0241234567", name: "Akua" },
});
window.location.href = paymentUrl;

After payment Paystack redirects to <your-callback-url>?orderRef=ORD-...&status=success. Your thank-you page:

const params = new URLSearchParams(window.location.search);
const order = await palinde.orders.get(params.get("orderRef"), customerPhone);
// or, poll until confirmed:
const order = await palinde.orders.waitForPayment(params.get("orderRef"), customerPhone);

Initialize

new PalindeSDK({
	key: "pk_live_...", // required — the merchant generates this in their dashboard
	baseUrl: "https://api.palinde.com/api/v1", // default
	timeoutMs: 30_000, // per-request timeout
	cartStorageKey: "palinde:cartToken", // localStorage key; pass `null` to disable (server-side)
	onError: (err) => Sentry.capture(err),
});

| Option | Default | Notes | | ---------------- | -------------------------------- | ---------------------------------------------------------------------------------------- | | key | — | Publishable API key. pk_live_… (all keys are live) | | baseUrl | https://api.palinde.com/api/v1 | Override for self-hosted / dev environments | | timeoutMs | 30000 | Aborts requests longer than this | | cartStorageKey | "palinde:cartToken" | Where the SDK persists the cart token. null disables — required for Node / Deno / edge | | onError | — | Hook fired on every error. Useful for telemetry |


Reference

palinde.store

| Method | Returns | Description | | ------- | -------------- | ------------------------------------------------------------------ | | get() | StoreProfile | Merchant identity — branding, branches, currency, hours, fee tiers |

palinde.products

| Method | Returns | Description | | -------------------------------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------- | | list({ page?, limit?, category?, search?, tag?, branchId? }) | ProductListPage | Paginated list. Pass branchId for branch-scoped stock. | | get(sku, { branchId? }) | Product | Single product including its variants[]. branchId scopes stock on parent + variants. | | all({ category?, search?, tag?, branchId? }) | AsyncIterable<Product> | Async iterator over every page |

ProductListPage is { items, page, limit, total, pages }.

for await (const p of palinde.products.all({ category: "Bags", branchId })) {
	console.log(p.sku, p.hasVariants ? p.variants.length + " variants" : "no variants");
}

Variants: when product.hasVariants is true, the customer must pick a variant before checkout. Use the variant's sku (not the parent's) for all subsequent cart, availability, and checkout calls. The parent SKU will be rejected with available: false, hasVariants: true.

palinde.availability

| Method | Returns | Description | | ------------------------------------- | -------------------------- | ----------------------------------------------------------------- | | check({ sku, quantity, branchId? }) | AvailabilityResult | Stock + price for one SKU. SKU can be a product or variant. | | checkBulk({ items, branchId? }) | AvailabilityBulkResponse | Stock + price for many SKUs in one call. Useful for cart refresh. |

const r = await palinde.availability.check({ sku: "BAG-001-RED-L", quantity: 2, branchId });
if (!r.available) showError(r.reason);
else updateLineTotal(r.lineTotal);

If the SKU is a parent product with variants, the response has available: false, hasVariants: true, and reason: "This product has variants — please choose a specific variant to order."

palinde.carts

| Method | Returns | Description | | --------------------------------------- | ---------------- | -------------------------------------------------- | | create({ branchId? }) | Cart | Create new anonymous cart, persist token | | current() | Cart | Read cart for the persisted token | | get(cartToken) | Cart | Read a specific cart | | ensure({ branchId? }) | Cart | Get current cart OR create one | | getCurrentToken() | string \| null | Read the persisted token (no API call) | | clearCurrent() | void | Forget the persisted token | | addItem({ sku, quantity, branchId? }) | Cart | Add a product or variant SKU | | updateItem({ itemId, quantity }) | Cart | Change quantity (note: itemId, not SKU) | | removeItem({ itemId }) | Cart | Remove a line | | clear() | Cart | Remove every line | | applyPromo({ promoCode }) | Cart | Apply promo (validated against current contents) | | clearPromo() | Cart | Remove applied promo | | quote() | CartQuote | Live totals — subtotal, discounts, fee, tax, total |

All methods accept an optional cartToken to override the persisted one, and an optional signal: AbortSignal for cancellation.

palinde.checkout

| Method | Returns | Description | | --------------------------------------------------- | ---------------- | ------------------------------------------ | | create({ customer, promoCode?, idempotencyKey? }) | CheckoutResult | Submit order, get Paystack hosted-page URL |

const { paymentUrl, orderRef, total, expiresAt } = await palinde.checkout.create({
	customer: { phone: "0241234567", name: "Akua" },
});
  • The SDK auto-clears the persisted cart token after success.
  • Pass your own idempotencyKey (UUIDv4) to safely retry on network errors. Without one, the SDK generates a fresh UUID per call.

palinde.orders

| Method | Returns | Description | | ---------------------------------------------------------------------- | ------------- | --------------------------------------------------------------------------------- | | get(orderRef, customerPhone) | PublicOrder | Single fetch | | waitForPayment(orderRef, customerPhone, { timeoutMs?, intervalMs? }) | PublicOrder | Polls until a terminal payment state (PAID, FAILED, EXPIRED, REFUNDED) or timeout |

palinde.delivery

| Method | Returns | Description | | ------------------------------------------------------------ | ------------------------ | ---------------------------------------------------------- | | quote({ branchId, toGpsCode?, toLat?, toLng?, weightKg? }) | DeliveryQuotesResponse | Price every delivery option from a branch to a destination |


Error handling

Every failure throws a PalindeError:

import { PalindeError } from "@palinde/sdk";

try {
	await palinde.carts.addItem({ sku, quantity: 5 });
} catch (err) {
	if (err instanceof PalindeError) {
		if (err.isStockConflict()) showToast("Just sold out — try another size.");
		else if (err.isAuthError()) showToast("API key invalid — contact the merchant.");
		else if (err.isCartGone()) {
			palinde.carts.clearCurrent();
			await palinde.carts.create();
		} else if (err.isRateLimited()) {
			await new Promise((r) => setTimeout(r, 2000));
		} else {
			showToast(err.message);
		}
	} else throw err;
}

Predicates: | Predicate | HTTP | Meaning | |---|---|---| | isStockConflict() | 409 | Stock changed — refresh and retry | | isAuthError() | 401 | Key invalid / revoked / expired | | isForbidden() | 403 | Origin not allowed, or cart belongs to another merchant | | isCartGone() | 410 | Cart checked out or abandoned — start a new one | | isRateLimited() | 429 | Back off |

Properties: statusCode, errorName, path, timestamp, raw (full API error envelope).


TypeScript

Everything's typed. Import types directly:

import type { StoreProfile, Product, ProductVariant, Cart, CartQuote, CheckoutResult, PublicOrder } from "@palinde/sdk";

Framework guides

React (hooks)

Full example at /examples/react.tsx. Wrap your tree with the providers:

import { PalindeProvider, CartProvider } from "@palinde/sdk/react-example";

<PalindeProvider>
	<CartProvider>
		<YourApp />
	</CartProvider>
</PalindeProvider>;

Then use the hooks:

function CartDrawer() {
	const { cart, quote, addItem, removeItem } = useCart();
	// ...
}

function ThankYou() {
	const { order, loading } = useOrder(orderRef, phone);
	// ...
}

Next.js (App Router)

Full example at /examples/nextjs/. Two SDK instances:

  • lib/palinde-server.tscartStorageKey: null for server components
  • lib/palinde-client.tsx — default storage, wrapped in "use client"

Catalog renders on the server (fast first paint, SEO). Cart + checkout are client components.

Framer

Framer Code Components run in the browser. Use the CDN import:

import { PalindeSDK } from "https://cdn.jsdelivr.net/npm/@palinde/sdk@1/dist/index.mjs";

const palinde = new PalindeSDK({ key: "pk_live_..." });

export default function ProductGrid() {
	const [products, setProducts] = useState([]);
	useEffect(() => {
		palinde.products.list().then((r) => setProducts(r.data));
	}, []);
	return <div>...</div>;
}

Webflow / Wordpress / vanilla JS

<script type="module">
	import { PalindeSDK } from "https://cdn.jsdelivr.net/npm/@palinde/sdk@1/dist/index.mjs";
	const palinde = new PalindeSDK({ key: "pk_live_..." });

	document.querySelectorAll("[data-add-to-cart]").forEach((btn) => {
		btn.addEventListener("click", async () => {
			await palinde.carts.ensure();
			await palinde.carts.addItem({ sku: btn.dataset.sku, quantity: 1 });
		});
	});
</script>

Vue 3

import { PalindeSDK } from "@palinde/sdk";
import { ref, onMounted } from "vue";

export const palinde = new PalindeSDK({ key: import.meta.env.VITE_PALINDE_KEY });

export function useStore() {
	const store = ref(null);
	onMounted(async () => {
		store.value = await palinde.store.get();
	});
	return { store };
}

Production keys

All API keys are live. Use small order amounts during development and refund them — there is no separate sandbox environment.


Server-side usage

Set cartStorageKey: null to disable localStorage access. Manage cart tokens yourself (e.g. in your session store):

const palinde = new PalindeSDK({
	key: process.env.PALINDE_KEY,
	cartStorageKey: null,
});

// Pass cartToken explicitly:
await palinde.carts.addItem({ cartToken: session.cartToken, sku: "X", quantity: 1 });

FAQ

Why doesn't addItem take the SKU directly like the example shows? It does — but as a named argument so it's hard to misuse: addItem({ sku, quantity }). Avoids the addItem(token, opts) vs addItem(opts) ambiguity that bites users in similar SDKs.

Can I add multiple items at once? Not in one call. Loop and add them one by one — the API rate limits are high enough this isn't an issue in practice.

The cart drawer is empty after the user pays — is that right? Yes. After checkout.create() succeeds, the cart is CHECKED_OUT (immutable). The SDK auto-clears the persisted token, so palinde.carts.ensure() will create a fresh cart on the user's next interaction.

Can the SDK call private merchant APIs? No. The publishable key only authorizes the public-API surface (catalog, cart, checkout, order tracking). It cannot list other merchants' orders, modify products, or trigger payouts — by design.


License

MIT.