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

@joai/sdk

v15.0.0

Published

JoAi public API SDK — build with your AI agents

Readme

@joai/sdk

The React entry point contains reusable building blocks for editable content on client websites. The components stay domain-neutral: sites provide identifiers, copy, styling hooks, and rendered fields; the SDK owns session handoff and the editing lifecycle.

Managed content editor

import { ManagedContentEditor } from '@joai/react'

<ManagedContentEditor
  config={{
    contentId: 'content-id',
    publicSlug: 'website-content',
    sessionStorageKey: 'website-content-session',
    team: 'team-slug',
  }}
  initialContent={content}
  title="Inhalte bearbeiten"
  labels={{
    edit: 'Bearbeiten',
    save: 'Entwurf speichern',
    saving: 'Wird gespeichert …',
    publish: 'Veröffentlichen',
    publishing: 'Wird veröffentlicht …',
    cancel: 'Abbrechen',
    toolbar: 'Inhaltseditor',
    unsaved: 'Nicht gespeicherte Änderungen',
  }}
  messages={{
    conflict: 'Der Inhalt wurde zwischenzeitlich geändert. Deine Eingaben bleiben erhalten. Bitte prüfe sie und versuche es erneut.',
    loadError: 'Der Inhalt konnte nicht geladen werden.',
    publishError: 'Die Änderungen konnten nicht veröffentlicht werden.',
    published: 'Die Änderungen wurden veröffentlicht.',
    saveError: 'Der Entwurf konnte nicht gespeichert werden.',
    saved: 'Der Entwurf wurde gespeichert.',
    sessionExpired: 'Deine Sitzung ist abgelaufen.',
    unsavedNavigation: 'Du hast nicht gespeicherte Änderungen. Seite wirklich verlassen?',
  }}
  imagePickerLabels={{
    applyCrop: 'Zuschnitt übernehmen',
    back: 'Zurück',
    choose: 'Bild auswählen',
    close: 'Schließen',
    cropDescription: 'Passe Bildausschnitt und Zoom vor dem Hochladen an.',
    cropTitle: 'Bild zuschneiden',
    dimensionsTooLarge: 'Die Bildabmessungen sind zu groß.',
    dimensionsTooSmall: 'Die Bildabmessungen sind zu klein.',
    drop: 'Bild hier ablegen',
    empty: 'Noch keine Bilder vorhanden.',
    field: 'Bearbeitbares Bild',
    invalidType: 'Bitte wähle eine Bilddatei.',
    libraryDescription: 'Wähle ein vorhandenes Bild oder lade ein neues hoch.',
    libraryTitle: 'Bildmediathek',
    loadError: 'Bilder konnten nicht geladen werden.',
    loading: 'Bilder werden geladen …',
    positionHorizontal: 'Horizontale Position',
    positionVertical: 'Vertikale Position',
    processing: 'Bild wird vorbereitet …',
    retry: 'Erneut versuchen',
    tooLarge: 'Das Bild ist zu groß.',
    upload: 'Bild hochladen',
    uploadError: 'Das Bild konnte nicht hochgeladen werden.',
    uploading: 'Wird hochgeladen …',
    zoom: 'Zoom',
  }}
  reauthenticateLabel="Anmeldung erneuern"
  sessionExpiredMessage="Deine Sitzung ist abgelaufen. Deine lokalen Änderungen bleiben erhalten."
  confirmCancel={() => window.confirm('Nicht gespeicherte Änderungen verwerfen?')}
  confirmLeave={() => window.confirm('Du hast nicht gespeicherte Änderungen. Seite wirklich verlassen?')}
  confirmPublish={() => window.confirm('Änderungen jetzt veröffentlichen?')}
>
  {children}
</ManagedContentEditor>

Use EditableContent, EditableImage, and useContentValue from @joai/react inside the editor. Pass server-fetched public content as initialContent to avoid a client-side loading state for visitors.

Configure each image field as a reusable, domain-neutral building block. Images are compressed to WebP at up to 2560 × 2560 pixels by default, while animated GIF and SVG files retain their original format. Cropping is opt-in per field:

<EditableImage
  field="heroImage"
  label="Titelbild"
  maxSourceBytes={40 * 1024 * 1024}
  maxUploadBytes={5 * 1024 * 1024}
  processing={{
    crop: { aspectRatio: 16 / 9, outputWidth: 1920 },
    compression: { maxWidth: 1920, maxHeight: 1080, mimeType: 'image/webp', quality: 0.84 },
    dimensions: { minWidth: 1280, minHeight: 720, maxPixels: 50_000_000 },
  }}
/>

Set compression: false or dimensions: false to disable only that stage, or processing={false} to upload the original image without client-side decoding, validation, cropping, or compression.

Session storage

useContentSession and createSessionTokenStore are exported for custom shells. A custom SessionTokenStore can be injected when a host needs a different persistence mechanism. The one-time authorization code is removed from the URL before the session token is persisted.

The managed editor starts the app authorization-code flow for reauthentication and stores the PKCE secret in local storage. Override reauthenticateUrl only when a host has a dedicated login callback.

createContentDraftStore uses sessionStorage by default. ManagedContentEditor wires it automatically so unsaved values survive refreshes and expired-session reauthentication without being shared across browser sessions.

Editing behavior

  • Cancel restores the currently loaded version.
  • Save is disabled when there are no local changes.
  • A dirty publish saves and publishes with one request.
  • Network failures keep local edits available for retry.
  • Refresh, close, and link/navigation attempts warn while edits are unsaved.
  • Editable text and image fields are visibly labelled in edit mode.
  • The image library supports loading, empty, error, byte/dimension validation, cropping, compression, keyboard, focus, and drag-and-drop states.
  • Expired authorization offers a sign-in action and restores locally persisted edits afterwards.
  • Version conflicts load the latest version while preserving local edits.
  • Public content cache entries are invalidated after authenticated mutations.

App authorization code

Apps that keep the bearer token on their server can use the SDK's PKCE helpers instead of implementing the flow themselves:

import { createAppAuthRequest, exchangeAppAuthCode } from '@joai/sdk'

const auth = await createAppAuthRequest({
  callbackUrl: 'https://app.example/api/auth/callback',
  team: 'team-slug',
})

// Store auth.state and auth.codeVerifier in a short-lived HttpOnly cookie,
// then redirect the browser to auth.url.

const response = await exchangeAppAuthCode({
  code: callbackCode,
  redirectUri: auth.redirectUri,
  codeVerifier: storedVerifier,
})

The callback origin must be registered for the team. The code is single-use, short-lived, bound to the canonical redirectUri, and verified with PKCE S256.

Hybrid guest carts

Storefronts can keep a guest cart without authenticating by generating an opaque cart token and passing it to the client:

const client = createClient({ env: 'mainnet', cartToken })
await client.cart.add('product', variationId, 2)

The API accepts X-Cart-Token for cart.*. When the same token is sent with an authenticated request, the guest cart is merged into the user cart and deleted.

Same-origin BFF helpers

Browser apps can keep the bearer token in an HttpOnly cookie instead of exposing it to client JavaScript. These helpers live behind optional subpaths so the root SDK stays browser-safe:

import { createBffFetch } from '@joai/sdk/bff'
import { createBffSessionCodec, handleBffProxy } from '@joai/sdk/server'
  • createBffFetch routes SDK calls through a same-origin BFF path.
  • createBffSessionCodec seals/unseals the bearer in an encrypted cookie.
  • handleBffProxy forwards an allowlisted set of API routes with origin and CSRF checks.