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

@viingx/extension-sdk

v0.1.0-preview.11

Published

SDK for building viingx web-client extensions (action dialogs, entity/page form elements)

Readme

@viingx/extension-sdk

Official SDK for building viingx web-client extensions — custom UI that runs inside the viingx Content Hub client. An extension is a small web app (any framework, or none) that the host loads in a sandboxed iframe and talks to over a typed postMessage bridge. This package provides that typed bridge.

The @viingx family: @viingx/create-extension (npm create @viingx/extension — scaffold a starter) · @viingx/content-hub-sdk (talk to the Hub's REST API) · @viingx/content-hub-codegen (generate a typed client) · this package.

Preview release (0.1.0-preview.x). The API may change before 1.0.

AI agents: this package ships an AGENTS.md with the non-guessable rules (incl. the content-hub-sdk interop recipe) — point your agent at it.

Extension types

| Type | Where it renders | Init function | | ------------------- | --------------------------------------------------------- | ----------------------------------- | | ActionDialog | A modal dialog or side panel launched from an action | initActionDialogAPI() | | EntityFormElement | A custom element inside the entity editor form | initEntityFormElementAPI() | | PageFormElement | A custom element inside a page / dashboard | initPageFormElementAPI() | | configuration UI | The admin dialog that configures a configurable extension | initConfigurationFormElementAPI() |

A side panel and a modal dialog are the same extension point: both are type: "ActionDialog" initialised with initActionDialogAPI(), differing only by properties.style ("sidePanel" vs "dialog") in extension.json. (npm create @viingx/extension offers them as separate starters.)

Install

npm install @viingx/extension-sdk

Quick start (an action dialog)

import { initActionDialogAPI } from "@viingx/extension-sdk";

const api = await initActionDialogAPI(); // optionally initActionDialogAPI<MyConfig>() to type getConfiguration()

// Let the host auto-size the iframe to your content (or use api.common.setHeight(px)).
await api.common.setAutomaticHeightAdjust(true);

// What the action was invoked on (e.g. the selected entities).
const input = await api.actionDialog.getInput();
await api.actionDialog.setTitle(input.definition.ui.label);

// Call the Content Hub REST API — the host provides the base URL and a session token.
// Recommended: the typed client from @viingx/content-hub-sdk (see below). No dependency
// required though — `api.common.getAPIURL()` + `getAuthToken()` also work with bare `fetch`.
// getAPIURL() may be relative; resolve to absolute so signUrl() works. The connection accepts the
// full /api/v1.0 base and strips the suffix itself.
const apiUrl = new URL(await api.common.getAPIURL(), window.location.href).href;
const hub = createContentHub(tokenConnection(apiUrl, () => api.common.getAuthToken()));
const types = await hub.types.list();

// Finish: hand a result back to the action handler.
await api.actionDialog.close({/* ... */});
// The imports for the snippet above:
import { createContentHub, tokenConnection } from "@viingx/content-hub-sdk";

Two things to know: getAPIURL() includes /api/v1.0 and may be relative — pass it straight to the connection (it accepts the full base and normalizes the suffix); resolve it to absolute (as above) only so signUrl() works, since a relative base makes the SDK's URL builders emit relative URLs signUrl() can't parse. And the session token rotates across re-logins, so pass it as an async provider that always fetches the current one.

initEntityFormElementAPI() / initPageFormElementAPI() follow the same shape, exposing common, entity, and a type-specific namespace (e.g. entityFormElement.updateEntity(...), entityFormElement.addEntityUpdateListener(...)).

Typed payloads & configuration

Every factory takes optional type parameters (defaulting to the untyped JSON shapes, so existing code is unaffected). Pair them with a generated payload type from @viingx/content-hub-codegen and the entity draft becomes fully type-checked:

import type { ArticlePayload } from "./content-hub.generated";

type MyConfig = { mode: "compact" | "full" };
const api = await initEntityFormElementAPI<ArticlePayload, MyConfig>();

api.entityFormElement.addEntityUpdateListener((entity) => {
    entity.content.payload.title; // typed — a typo is a compile error
});
const config = await api.entityFormElement.getConfiguration(); // MyConfig | undefined

Shared APIs

Every extension gets:

  • commonsetHeight / setAutomaticHeightAdjust, getAPIURL, getAuthToken, getUserInfo (UI locale, time zone, theme palette), getType(name) / getTypes().
  • entity (actions & form elements) — openEntityEditor(...), openEntityPicker(...).

Calling the Content Hub API — with @viingx/content-hub-sdk

Raw fetch works (see the quick start), but for anything beyond a call or two, use the server SDK — typed collections, queries, files, workflow. Two things to know when wiring the two packages together:

  • common.getAPIURL() returns the REST base including /api/v1.0, possibly relative — pass it straight to the connection (it accepts the full base and normalizes /api/v1.0 itself). Resolve it to absolute (new URL(url, location.href).href) only if you use signUrl.
  • common.getAuthToken() returns the user's current session token, which can rotate across re-logins — pass it as an async provider, not a captured string.
  • Showing Hub images? A plain <img src> can't authenticate from the sandboxed iframe (opaque origin) — mint the src with signUrl + getAuthToken(), or fetch the bytes via the download… twins. A bare URL-builder URL in <img src> gets a 401.
import { initActionDialogAPI } from "@viingx/extension-sdk";
import { createContentHub, tokenConnection } from "@viingx/content-hub-sdk";

const api = await initActionDialogAPI();
// getAPIURL() includes /api/v1.0 (may be relative) — the connection normalizes it; resolve to
// absolute only for signUrl.
const apiUrl = new URL(await api.common.getAPIURL(), location.href).href;

const hub = createContentHub(tokenConnection(apiUrl, () => api.common.getAuthToken()));
// When creating entities, use the host's default-content-locale contract (the same fallback logic
// the client's own editor applies): `(await api.common.getUserInfo()).defaultContentLocale` —
// `{ behavior: "use", locale }` apply silently; `{ behavior: "propose", locale }` pre-select but
// let the user confirm; `{ behavior: "choose" }` / `undefined` (older host) the user must pick.
// The user's `uiLocale` is a display language — never a content source locale.

const articles = await hub.entities.of("articles").query({}, { paging: { type: "offset", offset: 0, limit: 10 } });

For fully typed payloads, generate a client from your tenant's types with @viingx/content-hub-codegen and use its createHub(connection) — entity payloads inside form elements can then be cast to the generated XxxPayload types.

Packaging an extension

An extension is a zip containing an extension.json descriptor plus your built assets (index.html + JS). Minimal descriptor:

{
    "name": "myExtension",
    "type": "ActionDialog",
    "properties": { "style": "dialog", "width": "medium" }
}

name must match ^[a-zA-Z][a-zA-Z0-9]*$ — a letter first, then letters/digits only (no spaces or hyphens); the Hub rejects the upload otherwise. It's the extension's unique key per tenant: uploading again with the same name replaces it.

Upload the zip to a tenant via the admin UI, or programmatically with @viingx/content-hub-sdk: hub.extensions.upload(zipBlob).

properties are extension-point-specific. For action dialogs: style is "dialog" or "sidePanel"; width sizes it — dialog: small (default) / medium / large; side panel: medium (default) / large / extraLarge. Height is not a descriptor field — set it at runtime with common.setAutomaticHeightAdjust(true) or setHeight(px). configurable: true adds an admin configuration dialog (initConfigurationFormElementAPI()), read via getConfiguration() — omit it if there's nothing to configure.

Starter templates

An extension needs no framework or scaffolding — any web app that calls the matching init*API() and zips up with an extension.json works (see the quick start and packaging sections above). For a ready-to-run starter (React + Fluent UI + webpack, one per extension type, with a local dev server and a one-command deploy), scaffold a project with:

npm create @viingx/extension@latest

License

Apache-2.0 © viingx AG