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

@astrotech-sh/nuvicms-api-sdk

v0.3.1

Published

TypeScript HTTP client (ports-and-adapters) for the NuviCMS public API.

Readme

nuvicms-api-sdk

TypeScript HTTP client for the NuviCMS public API, built with a ports-and-adapters (hexagonal) architecture — zero runtime dependencies, DOM-free core, fully injectable I/O.

Extracted from vape-shop-022-site (originally nuvicms-client) for reuse across multiple client projects.

Status

Published to the public npm registry as @astrotech-sh/nuvicms-api-sdk version 0.1.0.

Installation

The SDK is published to the public npm registry — no custom registry configuration needed:

npm install @astrotech-sh/nuvicms-api-sdk

Content/CMS-shape types (pages, products, posts, etc.) are available from the ./content subpath and are type-only:

import type { CmsPage, CmsProduct } from "@astrotech-sh/nuvicms-api-sdk/content";

Quick Start

import { createNuviCmsClient } from "@astrotech-sh/nuvicms-api-sdk";

// Create a client with configuration
const client = createNuviCmsClient({
  apiBaseUrl: "https://api.nuvicms.com.br/api/public",
  siteKey: "your-site-key",
});

// Read store configuration
const config = await client.store.fetchConfig();
if (!config) {
  console.error("Store not configured");
  return;
}

// Validate a coupon
const validationResult = await client.coupons.validate("DISCOUNT10");
if (validationResult.valid) {
  console.log(`Discount: ${validationResult.coupon?.discountValue}`);
}

// Create an order
try {
  const order = await client.orders.create({
    customerRef: "customer-123",
    items: [
      { productId: "prod-1", quantity: 2 },
    ],
    delivery: { kind: "pickup" },
    payment: { method: "pix" },
  });
  console.log(`Order created: ${order.id}`);
} catch (error) {
  if (error instanceof NuviCmsConfigError) {
    console.error("Client not configured with apiBaseUrl");
  }
}

Public Surface

| Export | Type | Purpose | Documentation | |--------|------|---------|----------------| | createNuviCmsClient(config, ports?) | Factory | Creates a client instance | getting-started | | Configuration | | | | | NuviCmsConfig | Type | Client config shape | configuration | | NormalizedNuviCmsConfig | Type | Internal normalized shape | configuration | | Errors | | | | | NuviCmsError | Class | Base error type | errors | | NuviCmsHttpError | Class | Non-2xx HTTP response | errors | | NuviCmsEnvelopeError | Class | Malformed API envelope | errors | | NuviCmsTransportError | Class | Network/CORS/abort error | errors | | NuviCmsParseError | Class | JSON parse error | errors | | NuviCmsConfigError | Class | Missing apiBaseUrl | errors | | Ports | | | | | NuviCmsPorts | Type | Complete port set | ports | | HttpPort, StoragePort, LoggerPort, etc. | Types | Individual port contracts | ports | | AbortSignalLike | Type | Cancellation signal | ports | | Store Configuration | | | | | client.store.fetchConfig() | Method | Fetch store/catalog config | store-config | | client.store.trackFilterEvents() | Method | Send filter/search telemetry | filter-events | | StoreConfig, StoreConfigPix | Types | Store shape | configuration | | Coupons | | | | | client.coupons.validate(code, context?) | Method | Validate a coupon code | coupons | | CouponValidationResult, ValidatedCoupon | Types | Result types | coupons | | Customers | | | | | client.customers.getByClientRef(ref) | Method | Look up customer by ref | customers | | client.customers.upsert(payload) | Method | Create or update customer | customers | | CustomerData, CustomerAddress | Types | Customer shape | customers | | Orders | | | | | client.orders.create(request) | Method | Create an order | orders | | OrderRequest, OrderResponse | Types | Order shape | orders | | Forms | | | | | client.forms.getBySlug(slug) | Method | Fetch a form by slug | forms | | client.forms.submit(slug, values) | Method | Submit form values | forms | | CmsForm, CmsFormField, CmsFormValues | Types | Form shape | forms | | validateFieldValue, validateFormValues | Functions | Client-side validation | forms | | FormValidationCode, FormValidationResult | Types | Validation result | forms | | Pages | | | | | client.pages.getBySlug(slug, options?) | Method | Fetch a page by slug | pages | | Page, PageSection, PageModule, PageModuleData | Types | Page shape | pages | | PageSeo, PageFaqModuleData, PageFormModuleData, ... | Types | Module and SEO types (15+ types) | pages | | SEO & Public Content | | | | | client.sitemap.get() | Method | Fetch sitemap entries | sitemap | | client.robots.get() | Method | Fetch robots.txt directives | robots | | client.website.get() | Method | Fetch website configuration | website | | SitemapEntry, Robots, RobotsUserAgentGroup | Types | Sitemap and robots shape | sitemap, robots | | WebsiteInfo, WebsiteAddress, WebsiteIntegrations, ... | Types | Website info structure (20+ types) | website | | Pricing | | | | | calculateDiscounts(cart, rules) | Function | Compute order discounts | pricing | | resolveProductScopeIds(cart) | Function | Extract product IDs for coupon scope | pricing | | DiscountCartItem, DiscountResult | Types | Discount shapes | pricing | | Blog & Posts | | | | | client.posts.list(params?) | Method | Paginated post list, optionally filtered by category | posts | | client.posts.getBySlug(slug, options?) | Method | Fetch a single post by slug | posts | | client.posts.submitComment(slug, input, options?) | Method | Submit a comment on a post | posts | | client.blog.getFeedInventory(options?) | Method | Fetch the blog's RSS/feed inventory | blog | | Post, GetPostsParams, SubmitPostCommentInput | Types | Post shape and inputs | posts | | Catalog (Products) | | | | | client.products.list(params) | Method | Paginated product card list | products | | client.products.getBySlug(slug, options?) | Method | Fetch a single product by slug | products | | client.products.listReviews(slug, params) | Method | Paginated product review list | products | | client.products.submitReview(productId, payload, options?) | Method | Submit a product review | products | | client.productCategories.list(options?) | Method | Fetch all product categories | product-categories | | Product, ProductCard, ProductReview, ProductCategory | Types | Catalog shapes | products, product-categories | | Galleries | | | | | client.galleries.photos() | Method | Fetch all photo galleries | galleries | | client.galleries.videos() | Method | Fetch all video galleries | galleries | | PhotoGallery, GalleryPhoto, VideoGallery, GalleryVideo | Types | Gallery shapes | galleries | | Institutional | | | | | client.institutional.faq() | Method | Fetch all FAQ items | institutional | | client.institutional.team() | Method | Fetch all team members | institutional | | client.institutional.testimonials() | Method | Fetch all testimonials | institutional | | FaqItem, TeamMember, Testimonial | Types | Institutional content shapes | institutional | | Section Catalog | | | | | client.sectionCatalog.list() | Method | Fetch the available page-section descriptors | section-catalog | | SectionCatalogEntry | Type | Section descriptor shape | section-catalog | | Events | | | | | client.events.trackCta(event, idempotencyKey?) | Method | Track a CTA click/impression | events | | client.events.trackPost(event, idempotencyKey?) | Method | Track a post interaction | events | | CtaEvent, PostEvent | Types | Event shapes | events | | Payments | | | | | client.payments.getPublicKey(websiteExternalId) | Method | Fetch the payment provider's publishable key | payments | | PaymentsPublicKey | Type | Public key shape | payments | | Customers (extended) | | | | | client.customers.getByExternalId(externalId) | Method | Look up customer by external ID | customers | | Post Categories | | | | | buildPostCategoryTree(categories) | Function | Organize flat categories into a tree | content-types | | CmsPostCategory, CmsPostCategoryNode | Types | Category tree shapes | content-types | | Browser Utilities | | | | | cookieClientRefPort | Object | Browser session cookie adapter | ports | | ensureAnonCookie() | Function | Create anon session if needed | ports |

Type-only entry point: ./content exports only types and has no runtime code. Safe to import in type-only contexts and unbundled in tree-shaking.

Resources and Endpoints

| Facade Method | HTTP Method + Path | Retried | Requires Config | Documentation | |---------------|-------------------|---------|-----------------|---------------| | store.fetchConfig() | GET /store-config | Yes | No | store-config | | store.trackFilterEvents(events) | POST /filter-events | No | No | filter-events | | coupons.validate(code, context?) | GET /coupons/:code/validate | Yes | No | coupons | | customers.getByClientRef(ref) | GET /customers/:ref | Yes | No | customers | | customers.upsert(payload) | POST /customers | No | Yes | customers | | orders.create(request) | POST /orders | No | Yes | orders | | forms.getBySlug(slug) | GET /forms/:slug | Yes | No | forms | | forms.submit(slug, values) | POST /forms/:slug/submit | No | Yes | forms | | pages.getBySlug(slug, options?) | GET /pages/:slug | Yes | No | pages | | sitemap.get() | GET /sitemap | Yes | No | sitemap | | robots.get() | GET /robots | Yes | No | robots | | website.get() | GET /website | Yes | No | website | | posts.list(params?) | GET /posts | Yes | No | posts | | posts.getBySlug(slug, options?) | GET /posts/:slug | Yes | No | posts | | posts.submitComment(slug, input, options?) | POST /posts/:slug/comments | No | Yes | posts | | blog.getFeedInventory(options?) | GET /blog/feed-inventory | Yes | No | blog | | products.list(params) | GET /products | Yes | No | products | | products.getBySlug(slug, options?) | GET /products/:slug | Yes | No | products | | products.listReviews(slug, params) | GET /products/:slug/reviews | Yes | No | products | | products.submitReview(productId, payload, options?) | POST /products/:productId/reviews | No | Yes | products | | productCategories.list(options?) | GET /product-categories | Yes | No | product-categories | | galleries.photos() | GET /galleries/photos | Yes | No | galleries | | galleries.videos() | GET /galleries/videos | Yes | No | galleries | | institutional.faq() | GET /faq | Yes | No | institutional | | institutional.team() | GET /team | Yes | No | institutional | | institutional.testimonials() | GET /testimonials | Yes | No | institutional | | sectionCatalog.list() | GET /section-catalog | Yes | No | section-catalog | | events.trackCta(event, idempotencyKey?) | POST /cta-events | No | Yes | events | | events.trackPost(event, idempotencyKey?) | POST /post-events | No | Yes | events | | payments.getPublicKey(websiteExternalId) | GET /websites/:id/payments/public-key | Yes | No | payments | | customers.getByExternalId(externalId) | GET /customers/:externalId | Yes | No | customers |

Retry policy: Only idempotent GETs are retried on transient failures. POST (state-changing) and telemetry calls are never retried. Requires Config: Mutations (orders.create, customers.upsert, forms.submit) throw NuviCmsConfigError if apiBaseUrl is missing; reads silently return null or skip the call. SEO endpoints (sitemap.get(), robots.get(), website.get()) are best-effort and return empty/null on failure without throwing.

Error Handling

All errors inherit from NuviCmsError and can be caught with instanceof:

import {
  NuviCmsError,
  NuviCmsHttpError,
  NuviCmsConfigError,
} from "@astrotech-sh/nuvicms-api-sdk";

try {
  const order = await client.orders.create(request);
} catch (error) {
  if (error instanceof NuviCmsConfigError) {
    // Client not configured; silent for reads, loud for mutations
    console.error("Orders require apiBaseUrl");
  } else if (error instanceof NuviCmsHttpError) {
    // Non-2xx response
    console.error(`API error ${error.status}:`, error.body);
  } else if (error instanceof NuviCmsError) {
    // Other SDK error (transport, parse, envelope)
    console.error("SDK error:", error.message);
  }
}

See Error Handling for the complete hierarchy and contract.

Testing with Fake Ports

Inject fake I/O adapters in tests:

import { createNuviCmsClient } from "@astrotech-sh/nuvicms-api-sdk";

const fakeHttp = {
  async send(request) {
    if (request.path.includes("/store-config")) {
      return {
        status: 200,
        body: JSON.stringify({
          status: "success",
          data: { currency: "BRL", methods: ["pix"] },
        }),
      };
    }
    return { status: 404, body: "Not found" };
  },
};

const client = createNuviCmsClient(
  { apiBaseUrl: "https://api.test", siteKey: "test-key" },
  { http: fakeHttp } // Override the HTTP port
);

const config = await client.store.fetchConfig();
// config is { currency: "BRL", methods: ["pix"] }

See Ports for the full port interface and Testing for browser vs. Node patterns.

Architecture

createNuviCmsClient(config, ports)
  |
  +-- client.ts (facade)
       |
       +-- resources/*.ts (store, coupons, customers, orders, forms)
            |
            +-- transport/request.ts:sendRequest() [single HTTP call site]
                 |
                 +-- ports/http.ts (injected; default: adapters/browser.ts)
                 +-- mappers/*.ts (pure raw -> domain translation)

The client is shaped as a facade over per-resource modules. Every network call funnels through a single HTTP port, which by default uses fetch. Config is resolved once at construction and never re-read. Every side-effecting capability (fetch, sessionStorage, document, crypto, cookies) lives in adapters/browser.ts — the only place ambient DOM/runtime globals are allowed. Tests or non-browser contexts inject their own I/O ports.

The read/telemetry asymmetry is intentional: store.fetchConfig() and trackFilterEvents() silently no-op and return { kind: "skipped" } if apiBaseUrl is missing; mutations (orders.create, customers.upsert, forms.submit) throw NuviCmsConfigError instead. A dropped read is recoverable; a dropped order or customer record is not.

Development

Install dependencies, build, and test:

npm install
npm run build   # TypeScript compilation (tsconfig.build.json)
npm test        # Vitest

Architecture Invariants

Three invariants are enforced by boundary tests (src/tests/boundary.test.ts and src/tests/dom-free.node.test.ts):

  • No outbound imports: No file under src/ imports outside src/.
  • No ambient globals outside adapters: No fetch, sessionStorage, document, console, crypto, or other DOM/Node globals outside src/adapters/ and test files.
  • Zero runtime dependencies: The SDK has no production npm dependencies; only dev dependencies (TypeScript, Vitest).

Documentation