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

@byteflow-solutions/sdk

v0.1.2

Published

Thin TypeScript SDK for the Byteflow data-service public REST API

Readme

@byteflow-solutions/sdk

Thin TypeScript SDK for the Byteflow data-service public REST API. Use this from Astro sites, widgets, and other external consumers instead of calling internal dashboard server functions.

Zero runtime dependencies — native fetch only.

Install

In this monorepo:

pnpm add @byteflow-solutions/sdk

External Astro repos (once published):

npm install @byteflow-solutions/sdk

Environment variables

| Variable | Required | Description | |----------|----------|-------------| | BYTEFLOW_ORG_ID | For site calls | Default organization id for the site |

The SDK ships with a default data-service URL (DEFAULT_BYTEFLOW_API_URL). You do not need to set BYTEFLOW_API_URL unless you are pointing at a non-production worker.

Do not expose BYTEFLOW_ORG_ID in client-side browser code for form or contact writes. Proxy those through a server route and collect Turnstile tokens in the browser only.

For event tracking, exposing the organization id in browser code is expected — the public ingest endpoint relies on origin checks and rate limits, not secrets. Analytics data may be noisy or spoofed; treat counts as directional, not authoritative.

Security model

Public SDK routes are protected server-side by:

  • Organization id on every public read/write
  • Origin/referer checks against the organization's configured site URL for browser requests
  • Required Origin/Referer on POST /events (browser-only ingest)
  • Rate limits per organization and IP, plus an org-wide cap on event ingest
  • Turnstile validation on public form submissions when TURNSTILE_SECRET_KEY is configured
  • 90-day retention on stored tracking events

The SDK does not ship secrets. Dashboard-only routes such as gallery upload/delete and places autocomplete live under /internal/* and require X-Byteflow-Internal-Secret.

Quick start

import { createByteflowSdk } from "@byteflow-solutions/sdk";

const sdk = createByteflowSdk({
  organizationId: process.env.BYTEFLOW_ORG_ID,
});

// Server-to-server only
const { contact } = await sdk.contacts.create({
  name: "Jane Doe",
  email: "[email protected]",
  source: "Zapier",
});

// Public website form submission
await sdk.forms.submit({
  to: "[email protected]",
  formId: "contact-form",
  turnstileToken: formTurnstileToken,
  message: {
    name: "Jane Doe",
    email: "[email protected]",
    message: "I'd like a quote.",
  },
});

// Build-time reads; Google place id is resolved server-side from the organization
const reviews = await sdk.reviews.list({ maxReviews: 5 });
const stats = await sdk.reviews.getStats({});

// Track a button click or any other custom event
await sdk.events.track({
  eventKey: "button.click",
  properties: {
    buttonId: "hero-cta",
    label: "Book now",
  },
});

Per-call organization override:

await sdk.forms.submit({
  organizationId: "other-org-id",
  to: "[email protected]",
  formId: "contact-form",
  message: { name: "Jane Doe" },
});

Astro usage

Build-time reads (reviews, stats)

---
import { createByteflowSdk } from "@byteflow-solutions/sdk";

const sdk = createByteflowSdk({
  organizationId: import.meta.env.BYTEFLOW_ORG_ID,
});

const reviews = await sdk.reviews.list({ maxReviews: 5 });
---
<ul>
  {reviews.map((review) => (
    <li>{review.authorName}: {review.rating}/5</li>
  ))}
</ul>

Form writes (API route)

Never submit forms from the browser with a bare organization id. Proxy through an Astro API route and pass the Turnstile token from the site widget:

// src/pages/api/contact.ts
import type { APIRoute } from "astro";
import { createByteflowSdk } from "@byteflow-solutions/sdk";

const sdk = createByteflowSdk({
  organizationId: import.meta.env.BYTEFLOW_ORG_ID,
});

export const POST: APIRoute = async ({ request }) => {
  const body = await request.json();

  await sdk.forms.submit({
    to: import.meta.env.CLIENT_NOTIFY_EMAIL,
    formId: "contact",
    turnstileToken: body.turnstileToken,
    message: body.message,
  });

  return new Response(null, {
    status: 302,
    headers: { Location: "/thanks" },
  });
};

Event tracking (browser button click)

Track custom events directly from the browser when the site origin matches the organization's configured URL:

---
import { createByteflowSdk } from "@byteflow-solutions/sdk";

const sdk = createByteflowSdk({
  organizationId: import.meta.env.BYTEFLOW_ORG_ID,
});
---

<button id="hero-cta">Book now</button>

<script define:vars={{ organizationId: import.meta.env.BYTEFLOW_ORG_ID }}>
  import { createByteflowSdk } from "@byteflow-solutions/sdk";

  const sdk = createByteflowSdk({ organizationId });
  const button = document.getElementById("hero-cta");

  button?.addEventListener("click", () => {
    void sdk.events.track({
      eventKey: "button.click",
      properties: {
        buttonId: "hero-cta",
        label: "Book now",
      },
      url: window.location.href,
      path: window.location.pathname,
      referrer: document.referrer || undefined,
    });
  });
</script>

Use stable machine-readable eventKey values and put button-specific details in properties.

API surface

| SDK method | HTTP route | Notes | |------------|------------|-------| | contacts.create() | POST /contacts | Server-to-server only | | forms.submit() | POST /submit-form | Requires Turnstile when enabled | | events.track() | POST /events | Origin required; per-IP + org rate limits; browser-only | | reviews.list() | GET /google-reviews | Requires organization id | | reviews.getStats() | GET /google-reviews?statsOnly=true | Requires organization id |

The SDK uses organizationId in its public API. The data-service wire format still sends clientId for backward compatibility with existing routes.

Errors

Non-2xx responses throw ByteflowError with status, optional stable code, and payload:

import { ByteflowError } from "@byteflow-solutions/sdk";

try {
  await sdk.forms.submit({ to: "x", formId: "contact", message: {} });
} catch (error) {
  if (error instanceof ByteflowError) {
    console.error(error.status, error.code, error.payload);
  }
}

Stable backend codes include INVALID_ORIGIN, ORIGIN_REQUIRED, RATE_LIMITED, BAD_TURNSTILE_TOKEN, and PLACE_NOT_CONFIGURED.

Custom fetch (Cloudflare service bindings)

The dashboard uses a service binding to reach data-service. Pass a custom fetch and optionally override baseUrl:

const sdk = createByteflowSdk({
  baseUrl: env.DATA_SERVICE_URL, // optional; defaults to DEFAULT_BYTEFLOW_API_URL
  fetch: (input, init) => env.DATA_SERVICE.fetch(input, init),
});

Publish readiness checklist

Before publishing or rolling out to client Astro sites:

  • [ ] Public routes in apps/data-service/src/hono/app.ts are limited to contacts, submit-form, events, google-reviews, and read-only gallery routes
  • [ ] Gallery upload/delete and places autocomplete are only reachable under /internal/*
  • [ ] Each organization has organization.url configured for origin checks
  • [ ] INTERNAL_SERVICE_SECRET is set on both data-service and user-application workers
  • [ ] TURNSTILE_SECRET_KEY is set on data-service when public forms are enabled
  • [ ] RATE_LIMIT_KV is bound in production
  • [ ] Astro sites proxy writes through server routes and pass Turnstile tokens
  • [ ] pnpm run --filter @byteflow-solutions/sdk test passes
  • [ ] pnpm run --filter data-service test passes

Build

pnpm run --filter @byteflow-solutions/sdk build

Output is emitted to packages/sdk/dist.

Related docs