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

@business-nxt/app-messaging

v3.0.2

Published

Helper library to build applications for Business NXT

Readme

@business-nxt/app-messaging

Helper library to build applications for Business NXT

How it fits together

Your app runs inside an iframe served by the Business NXT apps proxy:

Business NXT (parent)
  └─ apps proxy frame   (holds the auth token; relays messages; injects appName)
       └─ your app      (uses this library; posts to window.parent = the proxy)

Communication is postMessage both ways. This library wraps that protocol: SendMessage for the request/reply message contract, and ExecuteGraphQL for authenticated GraphQL through the proxy relay (your app never sees the bearer token).

Messages that fail validation come back as { messageType: "error", reason: "schema-violation" }. A few fields are owned by the proxy, not the app: appName on dialog-request is injected by the proxy on every relayed message and cannot be set by the consumer.

Listen for changes from Business NXT

Your app and Business NXT communicate using postMessage back and forth. This means that you have to listen for messages with a messageType set.

The message types Business NXT will post are selection-state, edit-session, dialog-response, and error.

import type { Message } from "@business-nxt/app-messaging";

window.addEventListener("message", (event) => {
  const data = event.data as Message;
  switch (data?.messageType) {
    case "selection-state":
      // do something as selection changed
      break;
    case "edit-session":
      // do something as the session have changed edit mode
      break;
    case "dialog-response":
      // user closed a dialog you opened with `dialog-request`
      break;
    case "error":
      // a previous request failed (e.g. schema-violation or overlay-already-open)
      break;
  }
});

Request the current selection state

import { SendMessage } from "@business-nxt/app-messaging";

const selection = await SendMessage({ messageType: "selection-state-request" });

Pass toParent: true if you want the parent table that your app is joined to instead of the currently focused table. The default is false.

const parentSelection = await SendMessage({
  messageType: "selection-state-request",
  toParent: true,
});

selection

{
  "messageType": "selection-state",
  "id": "3dc32970-c15e-4227-84dc-0f8c3bba7e13",
  "table": "associate",
  "focusedRow": {
    "associateNo": 9
  },
  "selectedRows": [{ "associateNo": 7 }, { "associateNo": 8 }],
  "fromParent": false
}

fromParent mirrors the toParent flag on the request: it is true when the selection came from the parent table this app is joined to, false when it came from the focused table.

Request a table refresh

This will refresh the order table in the current layout

import { SendMessage } from "@business-nxt/app-messaging";

const refreshResponse = await SendMessage({
  messageType: "refresh-data-request",
  table: "order",
});

refreshResponse

{
  "messageType": "ok"
  "id": "3dc32970-c15e-4227-84dc-0f8c3bba7e13",
}

Request the current edit session state

import { SendMessage } from "@business-nxt/app-messaging";

const editSessionStatus = await SendMessage({
  messageType: "edit-session-request",
});

editSessionStatus

{
  "messageType": "edit-session",
  "id": "3dc32970-c15e-4227-84dc-0f8c3bba7e13",
  "editing": true,
  "editSessionId": "a1b2c3d4-..."
}

editSessionId identifies the edit session and is optional - the host includes it when known (including on saved / discarded, where it is the id of the session that just ended).

Open a dialog in Business NXT

Ask the host to show a modal dialog rendered with your markdown content. At least one of primaryButtonCaption / secondaryButtonCaption must be provided.

import { SendMessage, DialogResolution } from "@business-nxt/app-messaging";

const response = await SendMessage({
  messageType: "dialog-request",
  title: "Confirm delete",
  content: "Are you sure you want to delete **3 orders**?",
  primaryButtonCaption: "Delete",
  secondaryButtonCaption: "Cancel",
});

switch (response.result) {
  case DialogResolution.Primary:
    // user clicked the primary button
    break;
  case DialogResolution.Secondary:
    // user clicked the secondary button
    break;
  case DialogResolution.Closed:
    // user dismissed the dialog without choosing
    break;
}

When the host cannot serve the request (reason: "overlay-already-open" if a blocking overlay is open, reason: "schema-violation" if the message is malformed) SendMessage rejects with a MessagingError. Catch it to react to the failure:

import { SendMessage, MessagingError } from "@business-nxt/app-messaging";

try {
  const response = await SendMessage({ messageType: "dialog-request", ... });
} catch (err) {
  if (err instanceof MessagingError && err.reason === "overlay-already-open") {
    // queue the dialog for later, etc.
  }
}

Execute a GraphQL query against Business NXT

When your app is loaded through the Business NXT apps proxy, you can run authenticated GraphQL queries without ever seeing the bearer token. The proxy attaches the user's access token and forwards the request to the internal GraphQL API.

import {
  ExecuteGraphQL,
  GraphQLRequestError,
} from "@business-nxt/app-messaging";

const Query_GetCustomer = /* GraphQL */ `
  query GetCustomer($id: ID!) {
    customer(id: $id) {
      name
    }
  }
`;

try {
  const data = await ExecuteGraphQL<
    { customer: { name: string } | null },
    { id: string }
  >(
    Query_GetCustomer,
    { id: "42" },
    {
      operationName: "GetCustomer",
      signal: abortController.signal,
      timeout: 30000,
    },
  );
} catch (err) {
  if (err instanceof GraphQLRequestError) {
    // err.errors holds the structured GraphQL errors
  }
}

The promise rejects with GraphQLRequestError when the response carries errors, with a DOMException (AbortError) when the supplied signal aborts, and with a DOMException (TimeoutError) when the timeout (default 30s) is exceeded.

With GraphQL codegen

ExecuteGraphQL accepts any document whose type is structurally compatible with TypedDocumentNode<TResult, TVariables> - the standard shape emitted by @graphql-codegen (client-preset, typed-document-node) and @graphql-typed-document-node/core. When you pass such a document, TResult and TVariables flow through automatically and TypeScript will require the right shape:

import { GetCustomerDocument } from "./generated/graphql";

// TResult and TVariables are inferred from GetCustomerDocument
const data = await ExecuteGraphQL(GetCustomerDocument, { id: "42" });

At runtime the helper accepts:

  • a plain string,
  • a TypedDocumentString (the String subclass form from codegen client-preset),
  • a DocumentNode whose loc.source.body is populated (default for gql template tags and most codegen output).

Pre-print AST documents whose loc is stripped before passing them in.