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

@biab-dev/sdk

v0.9.35

Published

Business In A Box developer SDK — the data layer that lets you build customer-facing surfaces on your own domain while BIAB handles the operations stack behind the scenes. 0.9.x ships a comprehensive programmatic surface: storefront (products, categories,

Downloads

2,499

Readme

@biab-dev/sdk

Alpha: This package is in an alpha release. APIs and behavior may change without notice. It is not intended for production use yet — evaluate only in development, staging, or experiments.

Install

npm install @biab-dev/sdk
# or
pnpm add @biab-dev/sdk

Releases

Versioning convention: the patch field's tens digit identifies the feature group (e.g. 0.7.5x = chatbot work). Hot-fixes within a group stay in the same band — 0.7.51, 0.7.52, … — so consumers on ^0.7.5 pick them up automatically. New features bump the next band (0.7.6, then 0.7.61, 0.7.62, …).

0.8.x = schema-driven marketing flow. Major surface bump (still alpha). New entrypoints: defineSiteMarketingSchema(), @biab-dev/sdk/seo for typed JSON-LD builders, and a biab-dev CLI.

0.9.x = full programmatic consumer surface. The largest expansion yet: native storefront / cart / checkout / coupons / subscriptions, customer portal + tenant auth (createAuthHandler, getTenantSession, <SignIn/> <SignUp/> <SignOut/> useUser), blog, a paginated reviews wall, address autocomplete + shipping, programmatic local SEO (defineParallelPage()), the revalidation webhook channel (@biab-dev/sdk/next/revalidate + @biab-dev/sdk/adapters/revalidate), privacy-conscious analytics (@biab-dev/sdk/react-analytics + @biab-dev/sdk/analytics-core), and a three-state billing-lifecycle degradation contract. Detailed per-version notes below.

The 3 most recent releases are below. For the complete release history, see the changelog.

0.9.34

  • Read your visitor counts back. Analytics used to be write-only — the <BIABAnalytics/> tracker recorded pageviews and you could only see the totals in the dashboard. New client.site(siteId).analytics.pageViews() returns them:

    // Site-wide, all time
    const all = await biab.site(siteId).analytics.pageViews()
    if (all.available) console.log(`${all.total.views} views`)
    
    // A batch of pages in one round-trip (e.g. a view count per portfolio item)
    const each = await biab.site(siteId).analytics.pageViews({
      paths: ['/work/atrium', '/work/harbor'], days: 30,
    })

    Pass paths for a per-path breakdown (every requested path present, 0 if unvisited) plus a combined total; omit them for a site-wide total. days windows it. visitors approximates unique visitors (distinct daily-rotating anon id); views is the exact pageview number. Branch on .available — visitor analytics is a plan feature.

  • “Powered by BusinessDash” footer, now in the SDK. New export @biab-dev/sdk/react-attribution:

    import { BusinessDashFooter } from '@biab-dev/sdk/react-attribution'
    
    // In a Server Component — reads your plan's policy and renders accordingly
    <BusinessDashFooter client={biab.site(SITE_ID)} companyName="Acme Co" />

    BusinessDashFooterBanner is the presentational badge (pass siteId); BusinessDashFooter is a policy-aware Server Component. The badge carries a biab.app/?ref=<siteId> backlink — server-render it so it's visible. client.site(id).attribution exposes the policy read + an (optional) heartbeat. On Launch the badge is required; Growth/Scale may remove it — or keep it for a discount, toggled from Settings → Billing.

  • sync-records shows live progress. A large seed used to sit silent between “Discovered N rows” and the final tally. It now prints a progress line (rows done, running created/updated) — a single updating line in a terminal, a bounded trail in CI.

0.9.33

  • Read your custom database back. The data model used to be write-only from the SDK — you could push a schema and seed rows, then never read either. New client.site(siteId).dataModel.listRecords({ object }) returns a page of a custom object's rows, newest first, keyset-paginated (nextCursor); listAllRecords() walks every page for you. Relations come back expanded as links — record.relations.<field> is [{ recordId, object }], since a relation value lives in a link table, not on the row. Every declared relation field is present, [] when it has none, so an empty relation is never mistaken for a missing one.

    Not the same as collections / rows, which read Site Data. Rows seeded with sync-records live in the custom database and are reachable only through dataModel.

  • visibility per object — decide who can read a table. defineObject() takes an optional visibility:

    • private (default) — your server only, with a secret key.
    • authenticated — any signed-in customer (tenant auth).
    • public — anyone, from a publishable (pk_…) key in page JS.

    So a table you mark public is readable straight from the browser; a private one never is. Because it's part of your model, opening a table is a reviewed change — a push shows it on the dashboard's Review tab as an exposure, confirmed separately from destructive changes, and a human promotes it. You can also flip it from Site Builder → Site Data → Database → Schema.

    ⚠️ authenticated means every signed-in customer can read every row of that table. It is not per-customer ownership — never use it to keep one customer's data away from another's.

  • sync-data-model plans now flag exposure. Opening a table to a wider audience shows up as its own EXPOSURE group in the printed plan, distinct from destructive changes — a change that loses no data but can't be undone once the data's been read shouldn't read as "you might lose rows".

  • New scope metadata:read_records. Reads from the custom database need it. Scopes freeze when a key is minted, so if you get "Missing scope metadata:read_records", issue a fresh key from Site Builder → Developer — it grants every org-safe scope.

0.9.32

  • Corrected the dashboard locations the CLI prints. sync-data-model pointed at "CRM → Settings → Data Model", which was wrong twice over: the review + promote surface moved to Site Builder → Site Data → Database (beside Schema, since both are artifacts pushed from a repo for a human to review), and "Static Data" is now Site Data. sync-schema / sync-content updated to match.

Entrypoints

| Import path | Contents | | --- | --- | | @biab-dev/sdk | Main client, auth handler, all contract types | | @biab-dev/sdk/react | Embed iframes, chatbot inline, auth links, useUser, marketing page hooks | | @biab-dev/sdk/seo | JSON-LD builders + renderJsonLdNodes / renderJsonLdToHtml | | @biab-dev/sdk/marketing-schema | defineSiteMarketingSchema, section builder | | @biab-dev/sdk/contracts | Bare zod schemas + TS types (tree-shakeable) | | @biab-dev/sdk/proxy | Customer-portal proxy helpers |


Reserved page paths

When you attach a custom hostname to your org (pay.acme.com, acme.com, etc.), BIAB serves a small set of transactional public routes on that hostname so quote / contract / invoice links emailed to your customers stay on your own domain. Those routes are reserved — any marketing page (or contentSync.pages[].pageKey, or BIAB site-builder page slug) declared at one of them will be rejected by both sync-schema and sync-content, and by the site-builder createPage / updatePage mutations, with a clear error.

Reserved paths (and everything under them):

  • quotes, quotes/accept
  • contracts, contracts/sign
  • invoices, invoices/pay
  • portal

If the CLI exits with "<path>" is reserved by the BIAB platform., rename the offending page (e.g. quotesour-quotes, contractslegal/contracts) and re-run the sync.

Exact source of truth: src/reserved-paths.ts (mirrored server-side at src/server/sites/reserved-page-paths.ts).


0.8 — Schema-driven marketing content

1. Declare your schema

Create biab.config.ts at your project root:

import { defineSiteMarketingSchema, section } from "@biab-dev/sdk";
import { z } from "zod";

export default defineSiteMarketingSchema({
  brandTokens: ["company.name", "company.phone", "company.email"],
  sections: {
    hero: section({
      label: "Hero",
      schema: z.object({
        title: z.string(),
        subtitle: z.string().optional(),
        primaryCta: z.object({ label: z.string(), href: z.string() }).optional(),
      }),
      defaults: { title: "Welcome" },
      ui: { subtitle: { widget: "textarea" } },
    }),

    faq: section({
      label: "FAQ",
      schema: z.object({
        items: z.array(z.object({ q: z.string(), a: z.string() })),
      }),
    }),

    // Managed-data section — items come from BIAB project media
    gallery: section({
      label: "Gallery",
      schema: z.object({
        layout: z.enum(["grid", "masonry"]).default("grid"),
        maxItems: z.number().int().positive().default(12),
      }),
      dataSource: {
        kind: "project-media",
        configureHref: "/dashboard/projects",
      },
    }),
  },
});

Valid dataSource.kind values: "project-media" · "blog-posts" · "services-products"

2. Set environment variables

BIAB_API_KEY=...                                   # needs marketing:write_schema scope
BIAB_SITE_ID=...                                   # UUID from Site Builder → Developer
BIAB_PACKAGE_API_BASE_URL=https://your-host.tld    # auto-normalised to /api/package/v1

Site Builder vs visitor HTML — when changes actually show up

Operators often confuse “saved in BIAB” with “the public homepage changed.” They are connected only through whatever your frontend wires up.

There are three separate ideas:

| Layer | What happens | Latency mental model | | --- | --- | --- | | BIAB datastore + package API | Dashboard writes go through BIAB servers. client.site(siteId).marketing.getPageBundle({ pageKey, locale }) always reads the platform’s notion of published marketing data — not your CSS bundle. | After a successful server write + successful read, milliseconds to seconds depending on infra. | | Your SSR / ISR / CDN | The HTML you serve is assembled by Astro, Next, etc. Static HTML is whatever was produced at build/export. | For static artifacts, edits don’t ship until HTML is rebuilt and redeployed (or ISR revalidated, etc.). | | Consumer-side caching | Your SDK wrapper may memoize bundles (see BIAB_BUNDLE_CACHE). CDNs attach Cache-Control. | Minutes to “never” depending on TTLs unless you deliberately bypass cache. |

Why Site Builder preview can diverge from production: previews talk to drafted or preview backends. Production Astro output: 'static' bakes bundles at build time unless you hydrate from the API in the browser (not recommended — exposes keys) or migrate to SSR.

Operational patterns:

  • Prefer SSR (or SSR + CDN with short TTL) for BIAB-heavy sites. Example: Astro output: 'server' with a runtime adapter (@astrojs/vercel, Node, etc.). Every request executes your page code → getPageBundle() → fresh markup. Cold starts trade off against API chatter.
  • Stay static-only for marketing sites that rarely change. Cheaper infra: accept that astro build && deploy (or webhook-triggered pipelines) gates updates — BIAB edits still land in the DB immediately but visitors read yesterday’s artifact until the redeploy completes.
  • Hybrid: Astro output: 'hybrid', flip export const prerender = false on routes that rely on BIAB while keeping deterministic pages static.
  • Never ship BIAB_API_KEY to the browser. Public env vars prefixed with PUBLIC_ leak to client bundles — keep privileged keys server-only (import.meta.env.BIAB_API_KEY).

Schema drafts vs promoted schema: Updating section shapes still flows through draft → Promote gates (needs website.manage). Copy edits on already-published schemas hit the bundle read path independently — but browsers still won’t magically update if your HTML remains static until rebuild.

The Astro reference site bundled with BIAB tooling (UrbanAirNYC) sets output: 'server' + @astrojs/vercel, optional bundle memoization via BIAB_BUNDLE_CACHE, and composes BIAB-managed SEO/json-ld helpers (resolveHeadFromBundle(), marketingJsonLdFromBundle()) on top of local JSON defaults.

3. Publish to draft

The CLI needs tsx to execute your TypeScript config file. Add it as a dev dependency:

pnpm add -D tsx

Then run via tsx so it can load biab.config.ts:

pnpm exec tsx node_modules/@biab-dev/sdk/dist/cli.js sync-schema

Or add a script to package.json for convenience:

{
  "scripts": {
    "sync-schema": "tsx node_modules/@biab-dev/sdk/dist/cli.js sync-schema --config biab.config.ts",
    "print-schema": "tsx node_modules/@biab-dev/sdk/dist/cli.js print-schema --config biab.config.ts"
  }
}

Then run:

pnpm sync-schema
# [biab-dev] Loaded schema with 3 section(s).
# [biab-dev] Checksum: 7f2a9e1d…
# [biab-dev] Uploaded draft v4.
# [biab-dev] Promote in the dashboard at Site Builder → Static Data → Schema.

CLI flags:

| Flag | Description | | --- | --- | | --config path | Override config file discovery (default: biab.config.ts → .mjs → .js) | | --dry-run | Validate locally without uploading |

print-schema dumps the resolved JSON-Schema artifact + checksum to stdout (useful for CI debugging).

4. Promote in the dashboard

Site Builder → Static Data → Schema shows a diff of added / removed / changed sections. Click Promote to make schema-shape changes live. That gate requires website.manage, not only an SDK key. Text/column edits for already-published sections still persist through the bundle read path independently (but static HTML won’t react until rebuilt unless you SSR).

5. Seed initial content from local JSON

A freshly-promoted schema gives operators an empty editor — every section starts blank. If you've already authored content as JSON files in your repo (e.g. while building the site against local fallbacks), the sync-content command bulk-imports the whole tree in one shot.

Convention: directory layout

The CLI walks a tree shaped like one of:

# Default layout (zero-config — just set `rootDir`)
src/content/
  en/
    home/
      hero.json
      stats.json
    about/
      page.json

# Or: page dirs nested under an intermediate segment, with shared
# cross-page sections in a sibling dir (declare `pages` explicitly)
src/content/
  en/
    shared/
      brand.json
      footer.json
    pages/
      home/hero.json
      about/page.json

Each leaf JSON file becomes one (pageKey, locale, sectionKey) row in BIAB.

Declare the mapping in biab.config.ts

Add a contentSync field next to sections:

// biab.config.ts
import { defineSiteMarketingSchema } from "@biab-dev/sdk";

export default defineSiteMarketingSchema({
  sections: { /* ... your schema ... */ },
  brandTokens: [ /* ... */ ],
  contentSync: {
    rootDir: "src/content",
    locales: ["en"],
    // Explicit page dirs — needed when your layout has `pages/` and
    // `shared/` segments. Omit for the default `<locale>/<page>/` layout.
    pages: [
      { dir: "{locale}/shared",       pageKey: "shared" },
      { dir: "{locale}/pages/home",   pageKey: "home" },
      { dir: "{locale}/pages/about",  pageKey: "about" },
      { dir: "{locale}/pages/services", pageKey: "services" },
    ],
    // Filename → schema sectionKey aliases. Supports both:
    //   "<basename>"           — global alias
    //   "<pageKey>/<basename>" — page-scoped alias (wins over global)
    sectionAliases: {
      "service-areas":  "serviceAreas",    // global: kebab → camel
      "home/marquee":   "trustMarquee",    // scoped: only inside home/
      "about/page":     "aboutPage",       // scoped: page.json varies
      "services/page":  "servicesPage",
    },
    // Sections whose values come from other dashboards (gallery from
    // media uploads, blogFeed from the blog editor, pricing from
    // services/products). The CLI warns on matches but doesn't upload.
    skipSections: ["gallery", "blogFeed"],
  },
});

If you don't supply pages, the CLI auto-discovers: every direct child of <rootDir>/<locale>/ becomes a page, with the dir name as the pageKey.

Add the script and required env

The API key needs marketing:write_content (distinct from marketing:write_schema — a content-sync key can't reshape the schema, and vice versa). Reuse the same BIAB_API_KEY / BIAB_SITE_ID / BIAB_PACKAGE_API_BASE_URL from sync-schema.

{
  "scripts": {
    "sync-content": "tsx node_modules/@biab-dev/sdk/dist/cli.js sync-content --config biab.config.ts"
  }
}

Run it

pnpm sync-content
# [biab-dev] Discovered 24 section file(s) under src/content.
#   ⚠ Skipping pages/home/marquee.json — alias resolves to "trustMarquee" ✓
# [biab-dev] POST .../sites/.../marketing/sections in 1 batch(es)…
# [biab-dev] Batch 1/1: ✓ 22 ✕ 2
#
# [biab-dev] ✓ 22 succeeded  ✕ 2 failed
#   ✕ en/contact/page — Required field "email" is missing.
#   ✕ en/quote/page — Field "tiers[0].price" expected number, got string.
#
#   Tip: pass --lax to skip validation for this run.

Per-row failures don't abort the rest of the batch — the CLI keeps going and prints a summary so you can see every validation gap in one pass.

CLI flags

| Flag | Effect | | --- | --- | | --config path | Override config discovery (default: biab.config.ts → .mjs → .js) | | --dry-run | Walk the tree and report discovery, but don't POST | | --lax | Skip server-side schema validation for this run — useful for first imports of legacy content that doesn't perfectly match the schema yet. Server still records revisions, so you can clean up incrementally in the dashboard. | | --note "first import" | Tag every revision row created in this run with a human note. Shows up in the V7 Settings tab's version timeline. |

Re-runs are safe

Every file POST becomes a versioned revision on the server, append-only. Running sync-content twice in a row with no file changes just writes a new revision tagged identical (the dashboard's restore picker still shows the lineage). It will NOT clobber dashboard edits with stale file content unless the file actually differs.

If you want to wipe a section's history and restart from the JSON, do it from Site Builder → Static Data → Settings → History → Restore in the dashboard — that's gated on website.manage, not the API key.

6. Read content in your pages

SSR / RSC (Next.js, Astro, etc.)

import { createBiabDevClient } from "@biab-dev/sdk";

const client = createBiabDevClient({
  apiKey: process.env.BIAB_API_KEY!,
  baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!,
});

const { sections, seo } = await client
  .site(process.env.BIAB_SITE_ID!)
  .marketing.getPageBundle({ pageKey: "home", locale: "en" });

// Each section is tagged ok|error — one bad row never crashes the page
const hero = sections.hero?.ok ? sections.hero.data : null;

With typed schema validation

import marketing from "./biab.config";

const bundle = await client
  .site(siteId)
  .marketing.getPageBundle({ pageKey: "home", locale: "en" });

// marketing.parseSection validates through your zod schema
const hero = marketing.parseSection("hero", bundle.sections.hero);
// → typed as z.output<typeof heroSchema> | null

React hook

import { useMarketingPageBundle } from "@biab-dev/sdk/react";
import marketing from "./biab.config";

function HomePage() {
  const bundle = useMarketingPageBundle({
    apiKey: process.env.NEXT_PUBLIC_BIAB_API_KEY!,
    baseUrl: process.env.NEXT_PUBLIC_BIAB_PACKAGE_API_BASE_URL!,
    siteId: process.env.NEXT_PUBLIC_BIAB_SITE_ID!,
    pageKey: "home",
    locale: "en",
  });

  if (bundle.status !== "ready") return null;

  const hero = marketing.parseSection("hero", bundle.bundle.sections.hero);
  const { seo } = bundle.bundle;

  return (
    <>
      <title>{seo.seoTitle}</title>
      {hero ? <h1>{hero.title}</h1> : null}
    </>
  );
}

Astro (two-tier: BIAB with local JSON fallback)

---
import { createBiabDevClient } from "@biab-dev/sdk";
import marketing from "../../biab.config";
import { getContent } from "../lib/i18n";   // existing local fallback

const apiKey = import.meta.env.BIAB_API_KEY;
const siteId = import.meta.env.BIAB_SITE_ID;
const baseUrl = import.meta.env.BIAB_PACKAGE_API_BASE_URL;

let heroContent = getContent(locale, "pages/home/hero"); // always the safe fallback

if (apiKey && siteId && baseUrl) {
  try {
    const client = createBiabDevClient({ apiKey, baseUrl });
    const bundle = await client.site(siteId).marketing.getPageBundle({
      pageKey: "home",
      locale,
    });
    const parsed = marketing.parseSection("hero", bundle.sections.hero);
    if (parsed) heroContent = parsed; // BIAB wins when configured
  } catch {
    // silently fall back to local JSON
  }
}
---

SEO & AIEO — @biab-dev/sdk/seo

Typed JSON-LD builders for every common schema.org type. One <script type="application/ld+json"> per node — Google indexes them independently.

import {
  biabSchemas,
  renderJsonLdNodes,
  renderJsonLdToHtml,
} from "@biab-dev/sdk/seo";

const nodes = [
  biabSchemas.localBusiness({
    siteUrl: "https://example.com",
    businessType: "HVACBusiness",   // any schema.org subtype
    name: "Urban Air NYC",
    telephone: "+1-646-535-4001",
    priceRange: "$$",
    areaServed: [{ name: "Manhattan, NY", type: "AdministrativeArea" }],
    aggregateRating: { ratingValue: "5.0", reviewCount: "120" },
  }),
  biabSchemas.faq([
    { question: "Do you offer 24/7 service?", answer: "Yes." },
  ]),
  biabSchemas.breadcrumb({
    siteUrl: "https://example.com",
    items: [{ name: "Home", url: "/" }, { name: "Services", url: "/services" }],
  }),
];

// One <script> string per node (for manual injection)
renderJsonLdNodes(nodes);   // → [{ type, html }, ...]

// Concatenated string (for dangerouslySetInnerHTML or Astro set:html)
renderJsonLdToHtml(nodes);  // → "<script type=\"application/ld+json\">...</script>\n..."

Available builders: localBusiness · organization · website · service · faq · breadcrumb · article · review · productOffer


React hooks (0.8 additions)

All new hooks are exported from @biab-dev/sdk/react alongside the existing chatbot / auth / marketing-pages hooks.

import {
  // Bundle (sections + SEO in one call)
  useMarketingPageBundle,
  useMarketingSection,
  useMarketingPageSeo,
  // Site config
  useMarketingLocales,
  // Schema
  usePublishedMarketingSchema,
  // Prefetch on hover
  useMarketingBundlePreloader,
} from "@biab-dev/sdk/react";

useMarketingPageBundle

const bundle = useMarketingPageBundle({
  apiKey, baseUrl, siteId,
  pageKey: "home",
  locale: "en",
  enabled: true,    // gate on consent, auth, etc.
});
// bundle.status: "idle" | "loading" | "ready" | "error"
// bundle.bundle.sections   → Record<key, {ok:true,data,source} | {ok:false,error}>
// bundle.bundle.seo        → SiteMarketingSeoData
// bundle.bundle.availableLocales → string[]
// bundle.refresh()         → re-fetch bypassing cache

useMarketingSection<T>

const hero = useMarketingSection<HeroContent>({
  apiKey, baseUrl, siteId,
  pageKey: "home",
  sectionKey: "hero",
  parse: (entry) => marketing.parseSection("hero", entry),
});
// hero.data: HeroContent | null

useMarketingBundlePreloader

Wire to <Link onPointerEnter> so the next page is warm before the visitor clicks:

const preload = useMarketingBundlePreloader({ apiKey, baseUrl, siteId });

<Link onPointerEnter={() => preload({ pageKey: "about" })} href="/about">
  About
</Link>

Previous surfaces (0.7.x and earlier)

Marketing pages (legacy — still works)

const site = client.site(siteId);

const { page } = await site.marketingPages.get("home");
// page.payload → the full JSON document from Site Builder → Static Data

The 0.7 marketingPages.* API is fully supported. The 0.8 server-side legacy adapter re-aggregates per-section rows back into the payload shape when no legacy row exists — existing consumers render unchanged.

Collections

await site.collections.list();
await site.rows.query("page-content", {
  filters: [{ fieldName: "pageKey", operator: "equals", value: "home" }],
  limit: 10,
});

Quick-start example

import { createBiabDevClient } from "@biab-dev/sdk";

const client = createBiabDevClient({
  baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!, // https://…/api/package/v1
  apiKey: process.env.BIAB_API_KEY!,
});

// Identity probe — always the first call in a fresh integration
const auth = await client.introspect();

const site = client.site(process.env.BIAB_SITE_ID!);

// ── 0.8 schema-driven bundle ──────────────────────────────────────────
const bundle = await site.marketing.getPageBundle({ pageKey: "home", locale: "en" });

// ── 0.7 legacy marketing page (still works) ───────────────────────────
const { page } = await site.marketingPages.get("home");

// ── Collections / data ────────────────────────────────────────────────
await site.collections.list();
await site.rows.query("page-content", {
  filters: [{ fieldName: "pageKey", operator: "equals", value: "home" }],
  limit: 10,
});

Tenant auth (sign-in / sign-up / sign-out)

The SDK includes per-tenant authentication built on WorkOS Organizations. Visitors who click "Sign in" are signed in to the org bound to your API key — not to BIAB itself.

Install the auth handler

createAuthHandler returns Fetch-standard handlers. Mount in any framework:

Next.js (App Router)

// app/api/biab-auth/[...biab]/route.ts
import { createAuthHandler } from "@biab-dev/sdk";

const handler = createAuthHandler({
  baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!,
  apiKey: process.env.BIAB_API_KEY!,
  callbackUrl: `${process.env.NEXT_PUBLIC_SITE_URL}/api/biab-auth/callback`,
});

export const GET = handler.GET;
export const POST = handler.POST;

Astro

// src/pages/api/biab-auth/[...biab].ts
import { createAuthHandler } from "@biab-dev/sdk";
import type { APIRoute } from "astro";

const handler = createAuthHandler({
  baseUrl: import.meta.env.BIAB_PACKAGE_API_BASE_URL,
  apiKey: import.meta.env.BIAB_API_KEY,
  callbackUrl: `${import.meta.env.PUBLIC_SITE_URL}/api/biab-auth/callback`,
});

export const GET: APIRoute = ({ request }) => handler.GET(request);
export const POST: APIRoute = ({ request }) => handler.POST(request);

Astro requires output: "server" (or "hybrid") for the auth handler to work.

Remix / SvelteKit / SolidStart / Hono / Cloudflare Workers

Pass the Fetch-standard Request directly — handler.GET(request) and handler.POST(request). For Node frameworks (Express, Fastify), bridge with @whatwg-node/server.

React components

import { SignIn, SignOut, useUser } from "@biab-dev/sdk/react";

export default function Nav() {
  const me = useUser();
  if (me.status === "loading") return null;
  if (me.status === "signed-in") return <SignOut>Sign out</SignOut>;
  return <SignIn>Sign in</SignIn>;
}

SSR session

import { getTenantSession, DEFAULT_AUTH_COOKIE_NAME } from "@biab-dev/sdk";
import { cookies } from "next/headers";

const cookieValue = (await cookies()).get(DEFAULT_AUTH_COOKIE_NAME)?.value;
const session = await getTenantSession({
  cookieValue,
  baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!,
  apiKey: process.env.BIAB_API_KEY!,
});

Configuration

| Env var | Where it comes from | | --- | --- | | BIAB_API_KEY | One-time reveal in Site Builder → Developer → Package API keys | | BIAB_SITE_ID | UUID shown above the key list in the same panel | | BIAB_PACKAGE_API_BASE_URL | "Package API base URL" — must be https:// in production; the SDK auto-normalises any path to /api/package/v1 |

Two production traps to avoid:

  1. http:// against a production host — fetch strips Authorization on cross-scheme redirects.
  2. Base URL missing /api/package/v1 — SDK versions ≤ 0.2.0 silently drop the pathname; 0.2.1+ auto-corrects it.

Publishing to npm

Build and dry-run

cd biab-dev
pnpm build
npm pack --dry-run

Confirm only dist/, README.md, LICENSE, and package.json are packed.

Publish

npm publish --access public

prepublishOnly runs pnpm build automatically.

Troubleshooting

| Error | Meaning | | --- | --- | | 404 Scope not found | The @biab-dev org doesn't exist on npm, or your account isn't a member. | | 403 Forbidden | Your npm user doesn't have publish rights for this scope. | | Web login during publish | Normal with 2FA — complete the browser prompt, then retry. |