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

@kitenzo/core

v0.2.6

Published

Framework-agnostic SDK for [Kitenzo Bundle Builder](https://apps.shopify.com/bundlebuilder). Provides an API client, bundle configuration state machine, types, and utilities for building custom bundle experiences on any JavaScript platform.

Downloads

1,088

Readme

@kitenzo/core

Framework-agnostic SDK for Kitenzo Bundle Builder. Provides an API client, bundle configuration state machine, types, and utilities for building custom bundle experiences on any JavaScript platform.

Use this package directly for vanilla JS, Vue, Svelte, or server-side integrations. For React, use @kitenzo/react which wraps this package with hooks and components.

Install

npm install @kitenzo/core

Quick Start

import { KitenzoClient, createBundleBuilder, addBundleToCart } from '@kitenzo/core';

// 1. Create a client
const client = new KitenzoClient({
    apiKey: 'kit_live_...',
});

// 2. Find your bundle
const bundles = await client.listBundles();
const bundleId = bundles[0].id;

// 3. Fetch bundle with full product data
const bundle = await client.getBundle(bundleId);

// 4. Build a bundle configuration
const builder = createBundleBuilder(bundle);

builder.subscribe(() => {
    const state = builder.getState();
    console.log('Selections:', state.selections);
    console.log('Section:', state.currentSectionIndex);
    console.log('Valid:', state.isComplete);
    console.log('Errors:', state.errors);
});

const section = bundle.sections[0];
builder.addItem(section.id, section.products[0].variants[0].id);

// 5. Submit and add to cart
const selections = builder.getState().selections;
const result = await client.submitBundle(bundle, selections);
await addBundleToCart(result, {
    addLines: (lines) => cart.linesAdd(lines),
    getAttributes: () => cart.attributes ?? [],
    setAttributes: (attrs) => cart.cartAttributesUpdate(attrs),
}, { bundle, selections });

Full Bundle Embed

Use createBundleEmbed to render the full admin-configured bundle experience — the same UI merchants see on their storefront — inside any container element. No custom UI code needed.

import { createBundleEmbed } from '@kitenzo/core';

const embed = createBundleEmbed(document.getElementById('bundle-root')!, {
    bundleId: 42,
    apiKey: 'kit_live_...',
    shopDomain: 'my-store.myshopify.com',
    onAddToCart: ({ lines, bundleContent }) => {
        // lines are Storefront API CartLineInput objects
        // Add them to your cart via cartLinesAdd
        cart.linesAdd(lines);

        // For native bundles, set the _bundles cart attribute so the
        // cart transform extension can apply the discount at checkout.
        if (bundleContent) {
            const existingBundles = JSON.parse(
                cart.attributes?.find(a => a.key === '_bundles')?.value ?? '{}'
            );
            existingBundles[bundleContent.configuredBundleId] = bundleContent;
            cart.cartAttributesUpdate([
                ...(cart.attributes ?? []).filter(a => a.key !== '_bundles'),
                { key: '_bundles', value: JSON.stringify(existingBundles) },
            ]);
        }
    },
    onError: (error) => console.error('Embed error:', error),
});

// Clean up when done:
embed.destroy();

The embed automatically loads the storefront script, fetches shop settings (currency, moneyFormat), and intercepts add-to-cart events — normalizing the payload to Storefront API format.

onAddToCart Payload

The onAddToCart callback receives an EmbedCartPayload with:

| Field | Type | Description | |-------|------|-------------| | lines | CartLine[] | Cart lines ready for cartLinesAdd (Storefront API format). | | bundleContent | BundleContentPayload \| undefined | Bundle metadata for native bundles. When present, write it to the _bundles cart attribute so the cart transform can apply discounts. |

BundleContentPayload contains:

| Field | Type | Description | |-------|------|-------------| | id | number | Bundle ID. | | configuredBundleId | number | Unique ID for this bundle configuration. Use as key in the _bundles attribute. | | title | string | Bundle title. | | discount | string | Encrypted discount string (consumed by the cart transform). | | image | string \| null | Bundle image URL. | | items | Array<{ variantId, count }> | Products in the bundle. |

Each cart line in lines includes a _bundle_data attribute containing configuredBundleId#parentVariantId#uniqueId. This is how the cart transform matches individual line items to their bundle. When using the embed, these attributes are set automatically. If you construct cart lines manually (e.g. re-adding items from a saved cart), every line item that belongs to a native bundle must have the _bundle_data attribute — without it, the cart transform cannot identify the line as part of a bundle.

Important: Both _bundle_data (on each line item) and _bundles (on the cart) are required for the cart transform to apply discounts. The embed does not write the _bundles cart attribute itself — your onAddToCart handler must do it.

Bundle Type Differences

All necessary line item attributes (_bundle_data, _bundle_id, _bundle_price, subscription fields, etc.) are included automatically in lines — pass them through to cartLinesAdd as-is.

Native bundles: bundleContent is present. You must write it to the _bundles cart attribute (see example above) — the cart transform reads it to apply the discount at checkout.

Single-product and multiple-products bundles: bundleContent is undefined. No cart attributes needed — the discount is already baked into the variant price. Just call cartLinesAdd(lines).

Note: Only one embed per page is supported.

Setup

1. Enable the Headless API

In the Kitenzo admin panel, go to Settings > Headless to enable the headless API.

2. Create an API Key

On the same page, click Create API key. Keys are prefixed kit_live_ (production) or kit_test_ (development).

API key security: kit_live_ and kit_test_ keys are publishable keys, safe to use in client-side code (similar to Stripe publishable keys). They can only read bundle data and submit configurations — they cannot modify bundles or access admin functionality. Do not confuse these with server-side secrets.

3. Configure Allowed Origins (CORS)

If you're calling the API from a browser, you must add your domain to the API key's allowed origins list. Go to Settings > Headless, click your API key, and add each origin (e.g. https://your-store.com). Requests from unlisted origins will be blocked by CORS.

API Client

const client = new KitenzoClient({
    apiKey: 'kit_live_...',       // Required — your publishable API key
    apiVersion: 'v1',             // Optional (default: 'v1')
    baseUrl: 'https://...',       // Optional — override the API base URL
});

When no baseUrl is provided, the SDK uses https://live.bb.eight-cdn.com/api/headless/{apiVersion}. Use baseUrl for local development (e.g. /api/headless/v1 behind a Vite proxy) or custom proxy setups — when provided, apiVersion is ignored.

Methods

| Method | Returns | Description | |--------|---------|-------------| | listBundles() | Bundle[] | Published bundles (lightweight, no product data). | | getBundle(id) | BundleDetail | Bundle with sections, products, and variants — everything needed to render a builder. | | submitBundle(bundle, selections, options?) | SubmitBundleResult | Submit builder selections — handles product mapping automatically. Accepts an optional { countryCode } for market-aware pricing. | | getSettings() | ShopSettings | Shop settings (currency, moneyFormat, features). Auto-fetched by KitenzoProvider in React. |

Variant IDs accept GID format (gid://shopify/ProductVariant/123) or plain numeric strings ("123").

API Endpoints

All endpoints require a Bearer token (Authorization: Bearer kit_live_...).

| Method | Endpoint | Description | |--------|----------|-------------| | GET | /api/headless/v1/bundles | List published bundles | | GET | /api/headless/v1/bundles/:id | Bundle detail | | GET | /api/headless/v1/bundles/:id/products | Product and variant data | | POST | /api/headless/v1/bundles/:id/configure | Validate and create configuration | | POST | /api/headless/v1/bundles/:id/price | Calculate price | | GET | /api/headless/v1/settings | Shop settings |

Rate limit: 100 requests/minute per API key (configurable).

Bundle Builder State Machine

createBundleBuilder(bundle) returns a state machine for step-by-step bundle configuration. It follows the subscribe/getState pattern, making it compatible with React's useSyncExternalStore, Svelte stores, or plain callbacks.

import { createBundleBuilder } from '@kitenzo/core';

const builder = createBundleBuilder(bundleDetail);

// Subscribe to state changes
const unsubscribe = builder.subscribe(() => {
    const state = builder.getState();
    renderUI(state);
});

// Mutations
builder.addItem(sectionId, variantId, quantity?);
builder.removeItem(sectionId, variantId);
builder.updateQuantity(sectionId, variantId, newQuantity);
builder.reset();

// Navigation
builder.nextSection();
builder.prevSection();
builder.goToSection(index);

// Queries
builder.getSectionQuantity(sectionId); // number

Snapshot Shape

builder.getState() returns a BundleBuilderSnapshot:

{
    selections: SectionSelections;       // Record<sectionId, BundleSelection[]>
    currentSectionIndex: number;
    currentSection: BundleSection | null;
    isSectionValid: boolean;
    isValid: boolean;
    isComplete: boolean;                 // all sections valid + all rules pass
    allItems: BundleSelection[];         // flat list of all selections
    errors: ValidationError[];           // limit rule / required product violations (empty when valid)
}

The snapshot is immutable — a new object reference is created on each state change (for efficient React reconciliation or shallow comparison).

Pricing

calculatePrice(bundle, selections, currency?)

Calculate bundle pricing from variant prices and discount rules. Handles flat discounts (percentage, fixed, price) and tiered discounts with all operation types.

The currency parameter is a label included in the response — pass the shop's currency from getSettings() so the returned PriceResponse.currency is accurate. If omitted, the field is empty.

import { calculatePrice } from '@kitenzo/core';

const settings = await client.getSettings();
const pricing = calculatePrice(bundle, builder.getState().selections, settings.currency);
console.log(pricing.originalPrice);    // "45.00"
console.log(pricing.discountedPrice);  // "40.50"
console.log(pricing.discountType);     // "percentage"
console.log(pricing.discountValue);    // "10.00"
console.log(pricing.currency);         // "CZK"

React users: Use the useBundlePrice hook instead — it returns pre-formatted prices (e.g. "$45.00") using the shop's moneyFormat automatically.

formatMoney(amount, moneyFormat)

Format an amount using a Shopify money format string. Supports all standard Shopify placeholders.

import { formatMoney } from '@kitenzo/core';

formatMoney(45, '${{amount}}');                           // "$45.00"
formatMoney(1234.5, '€{{amount_with_comma_separator}}'); // "€1.234,50"
formatMoney('99.99', '{{amount}} kr');                    // "99.99 kr"

Cart Utilities

addBundleToCart(result, cart, context?)

Add a configured bundle to a Shopify cart in one call. Builds cart lines, merges the _bundles cart attribute, and calls both mutations — so the cart transform can always apply the discount. Pass { bundle, selections } as the third argument for native bundles, which is required to produce the correct _bundle_data line attributes and _bundles cart attribute.

import { addBundleToCart } from '@kitenzo/core';

const result = await client.submitBundle(bundle, selections);
await addBundleToCart(result, {
    addLines: (lines) => cart.linesAdd(lines),
    getAttributes: () => cart.attributes ?? [],
    setAttributes: (attrs) => cart.cartAttributesUpdate(attrs),
}, { bundle, selections });

buildCartPayload(result, existingAttributes?, context?)

Lower-level alternative when you need direct control. Returns { lines, attributes } — you must call both cartLinesAdd and cartAttributesUpdate yourself. Pass { bundle, selections } as the third argument for native bundles.

import { buildCartPayload } from '@kitenzo/core';

const result = await client.submitBundle(bundle, selections);
const { lines, attributes } = buildCartPayload(result, cart.attributes, { bundle, selections });
cart.linesAdd(lines);
cart.cartAttributesUpdate(attributes);

buildCartLines(result)

Build cart lines only (without attribute merging). Useful when you manage cart attributes separately.

import { buildCartLines } from '@kitenzo/core';

const result = await client.submitBundle(bundle, selections);
const lines = buildCartLines(result);
cart.linesAdd(lines);

Section Utilities

computeSectionQuantity(selections, sectionId)

Returns the total quantity selected in a section.

isSectionMet(selections, section)

Returns whether a section's minimum/maximum quantity constraints are satisfied.

TypeScript Unions

The package exports typed union types for fields that accept a fixed set of values, providing autocomplete and compile-time safety:

| Type | Values | |------|--------| | BundleType | 'single-product' | 'multiple-products' | 'native' | | DiscountType | 'percentage' | 'fixed' | 'price' | | DiscountMode | 'flat' | 'tiered' | | ComparisonOperator | 'gt' | 'gte' | 'lt' | 'lte' | 'eq' | | TierConditionType | 'total_products' | 'bulk_buy' | 'total_price' | | LimitRuleType | 'bundle-price' | 'total-number-of-products' | ... |

Variant Availability

The BundleVariant.available field is a boolean indicating whether the variant can be selected. When available is false, the variant should be shown as disabled. The API does not currently provide a specific reason for unavailability — it may be out of stock, restricted by inventory policy, or excluded by bundle rules. Display unavailable variants as disabled to let customers see the full selection, and use the inventoryQuantity field (when present) to distinguish out-of-stock variants from other restrictions.

Troubleshooting

CORS errors -- Your origin is not in the API key's allowed origins list. Go to Settings > Headless, click your API key, and add your domain.

401/403 -- Verify the API key is correct, active, and headless is enabled.

Cart lines added but no discount -- Make sure cart attributes from buildCartPayload() are applied via cartAttributesUpdate(). If using the embed (createBundleEmbed), check that your onAddToCart handler writes bundleContent to the _bundles cart attribute — see the embed section above.

"Unable to add kit to cart" in console (embed mode) -- This happened in older versions when the embed tried to call Shopify Liquid endpoints (/cart.js, /cart/update.js) that don't exist on headless storefronts. Update to the latest version — the embed now skips these calls when onAddToCart handles the event.