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

agljs

v2.0.0

Published

A lightweight framework for integrating web apps with Epic's Active Guidelines platform, using modern JavaScript features. Manage action queuing, event handling, subscriptions, and state within Epic Hyperspace.

Readme

AGL.js | Epic Active Guidelines

A typed client for Epic's Active Guidelines (AGL) platform. Call do() to run actions, listen for events, and read session details — the handshake, tokens, and message plumbing are handled for you.

It handles:

  • Detecting whether the app is running inside Epic
  • Communicating with the Epic backend
  • Triggering and queuing actions in Hyperspace
  • Subscribing to and handling AGL events, including print requests
  • App state and navigation history
  • Clean teardown of the persistent message listener

Table of Contents

  1. Getting Started
  2. Configuration
  3. Usage
  4. Parameters
  5. Methods
  6. Queuing
  7. Error Handling
  8. Subscriptions
  9. State Management
  10. Navigation History
  11. Teardown
  12. Migrating from 1.x

Disclaimer

AGL.js is not affiliated with or endorsed by Epic Systems Corporation. It is intended solely for use by organizations with active Epic licenses. Please ensure compliance with your Epic agreements before using or distributing this library.

Getting Started

📦 Installation

Install via npm or yarn:

npm install agljs
# or
yarn add agljs

TypeScript

import AGL from 'agljs'

const agl = new AGL()
if (await agl.active) console.log(agl.details.availableActions)

JavaScript (ESM)

import AGL from 'agljs'

const agl = new AGL()
if (await agl.active) console.log(agl.details.availableActions)

JavaScript (CommonJS)

const AGL = require('agljs')

const agl = new AGL()
agl.active.then(active => active && console.log(agl.details.availableActions))

💾 Script Tag (download or CDN)

Download agl.min.js from the releases page, or load it from a CDN such as jsDelivr:

The script tag exposes the class as the global AGL:

<!-- local: <script src="path/to/agl.min.js"></script> -->
<script src="https://cdn.jsdelivr.net/npm/agljs/js/agl.min.js"></script>
<script>
  const agl = new AGL();

  agl.active.then(function(active) {
    if (active) console.log(agl.details.availableActions);
  });
</script>

Configuration

The AGL constructor accepts an optional configuration object with the following optional parameters:

| Parameter | Type | Description | Default | |----------------|----------------|--------------------------------------------------------------------|---------| | debug | boolean | Enables debug logging for troubleshooting | false | | timeout | number | Timeout (in milliseconds) before failure for action responses | 2000 | | targetOrigin | string | Restricts inbound/outbound messages to a specific Epic origin | '*' | | subscribe | object | An object containing events to subscribe to during the handshake | {} | | onError | function | Callback for handling errors | null | | onNavigate | function | Callback for navigation events (e.g., back/forward) | null | | onReload | function | Callback for reload (history package) events | null | | onAGLEvent | function | Callback for custom AGL events | null | | onSubscribed | function | Callback for handling subscription results | null | | onPrint | function | Callback fired when Epic requests a print | null |

Debugging tip: set debug: true in the constructor as your first step. Because the handshake runs at construction, only a constructor-time debug flag captures it — this is the most useful log for diagnosing initialization.

Note: subscribe and targetOrigin are fixed at first construction. Re-constructing AGL updates only debug, timeout, and callbacks.

Example Configuration

const agl = new AGL({
   debug: true,
   timeout: 5000,
   subscribe: {
      "Epic.Common.RequestToCloseApp": { PauseDuration: 300 },
   },
   onError: (error) => console.error('Error:', error),
   onNavigate: (event) => console.log('Navigation event:', event.direction),
   onReload: (payload) => console.log('Reload payload:', payload),
   onPrint: () => window.print(),
});

Usage

All actions must wait for the AGL handshake to complete and confirm the app is running inside Epic, via if (await agl.active).

Example:

const agl = new AGL();

if (await agl.active) {
   agl.do('Epic.Clinical.Informatics.Web.SaveState', { state: 'example' })
      .then((success) => console.log('State saved:', success))
      .catch((err) => console.error('Error saving state:', err));
}

Example:

const agl = new AGL();
if (!await agl.active) throw new Error('AGL is inactive! Not running in Epic.');

agl.on('navigate', (event) => {
   console.log('User navigated:', event.direction);
});

Parameters

active (read-only)

Always returns a Promise<boolean> that resolves to whether AGL is active and initialized. await it before performing actions:

if (await agl.active) console.log('AGL is initialized and ready.');

details (read-only)

Provides information from Epic about the current AGL context. Properties include:

| Property | Type | Description | |---------------------------------------|-------------------|-----------------------------------------------------| | availableActions | string[] | List of actions supported in the current context | | actionsNotRequiringUserConfirmation | string[] | Actions Epic will run without a confirmation prompt | | currentState | string \| null | State restored from the handshake (see below) | | interfaceVersion | string \| null | Version of the AGL JavaScript interface | | readOnly | boolean | Indicates if the AGL context is read-only | | token | string \| null | Token required for posting messages to Epic |

Example:

console.log('Token:', agl.details.token);
console.log('Available actions:', agl.details.availableActions.join(', '));

debug (write-only)

Enables or disables debug logging dynamically

Example:

agl.debug = true; // Enable debug logging

Methods

do(action, args = null, haltOnError = false)

Executes an action within AGL.

| Parameter | Type | Description | |----------------|------------|----------------------------------------------------------------------------------------------| | action | string | The name of the action to perform | | args | object | Optional Arguments to pass with the action | | haltOnError | boolean | Optional Stops the queue if this action fails, preventing subsequent actions from running |

do() automatically waits for the handshake to finish before running, so you do not need to guard every call with await agl.active. It resolves false if AGL never initializes (e.g. not running in Epic).

Example: Basic Action

agl.do('SaveState', { state: 'example' });

For convenience, actions without a full namespace (i.e., actions without a dot) are automatically prefixed with Epic.Clinical.Informatics.Web. To override this behavior, include the full namespace directly:

Example: Full Namespace Action

if (await agl.active) {
   agl.do('Custom.Namespace.Action', { someArg: 'value' });
}

on(eventName, callback)

Registers a callback for specific AGL events. | Parameter | Type | Description | |-----------------|------------|----------------------------------------------------------| | eventName | string | The name of the event to listen for | | callback | function | The callback function to execute when the event occurs |

Events types:

| Event Name | Example Response | |-----------------|-----------------------------------------------------------------------| | error | { message: 'Error description', details: ['Detail 1', ...] } | | navigate | { direction: 'Back' } | | reload | The raw historyPackage payload from Epic (see note below) | | aglEvent | { name: 'Event name', args: { arg1: ... } } | | subscribed | { EventName: 'Event name', SubscriptionSuccess: boolean } | | print | (no arguments) — fired when Epic requests a print |

reload payload: Epic delivers the navigation-history package under historyPackage. Its inner shape is passed through unchanged as a Record<string, unknown>; inspect it against your Epic build (enable debug) rather than assuming key names. This is distinct from state restoration — see State Management.

Example:

agl.on('navigate', (event) => {
   console.log('User navigated:', event.direction);
});

Example: Chaining listeners

agl
   .on('navigate', (event) => console.log('User navigated:', event.direction))
   .on('reload', (payload) => console.log('History package:', payload));

Queuing

Actions are added to a queue and executed sequentially to avoid race conditions. By default, the queue continues processing even if an action fails. However, setting haltOnError in do to true stops the queue when that action fails, ensuring dependent actions are not executed. A timeout is treated as a lost connection: it tears the session down entirely (see Teardown).

Example:

agl.do('SaveState', { state: 'example' }, true) // Stop queue if SaveState fails
   .catch((error) => console.error('SaveState failed:', error));

agl.do('CloseActivity', null) // Will only execute if SaveState succeeds
   .catch((error) => console.error('CloseActivity failed:', error));

Advanced Usage

Error Handling

Without an error handler, a failed action rejects with an Error. With a handler, the callback receives the structured error and the action resolves false. If the handler throws, the action rejects with that error.

Overriding Error Handling

You can override internal error handling by providing a custom onError callback in the configuration.

Example: During instantiation

const agl = new AGL({
   onError: (error) => {
      console.error('Custom error handler:', error.message);
   },
});

Example: After instantiation

agl.on('error', (error) => {
   console.error('Custom error handler:', error.message);
});

Subscriptions

You can specify subscriptions during initialization using the subscribe property. Subscriptions allow your application to register for specific events, such as changes in patient demographics or requests to close the app.

Confirming Subscriptions

To confirm that each subscription was successfully processed during the handshake, you can define an onSubscribed callback. This callback receives the subscription results, allowing you to verify whether your subscriptions were accepted by Epic.

Example:

const agl = new AGL({
   subscribe: {
      "Epic.Patient.Demographics.Updated": { IncludeHistory: true },
      "Epic.Common.RequestToCloseApp": { PauseDuration: 200 }
   },
   onSubscribed: (results) => {
      console.log('Subscription handshake completed:', results);
   },
});

Listening for Subscribed Events

After subscriptions are successfully established, your application can handle the events triggered by those subscriptions using the aglEvent handler.

Example:

agl.on('aglEvent', (event) => {
   if (event.name === 'Epic.Patient.Demographics.Updated') {
      console.log('Patient demographics updated:', event.args);
   }
   if (event.name === 'Epic.Common.RequestToCloseApp') {
      agl.do('Epic.Common.CloseApp',  {
         CanClose: true
      });
   }
});

State Management

AGL.js supports saving and restoring app state during transitions or hibernation.

Saving State

To save the current state of your application, use the SaveState action.

Example: Simple string

agl.do('SaveState', { state: 'dashboard' });

Example: Complex state

agl.do('SaveState', {
   state: JSON.stringify({ tab: 'overview', filters: { active: true } })
});

Restoring State

Epic returns the previously saved state on the handshake, exposed as details.currentState. Read it once AGL is active — it is null when no state was saved:

Example:

const agl = new AGL();

if (await agl.active && agl.details.currentState) {
   const restored = JSON.parse(agl.details.currentState);
   console.log('Restored state:', restored);
   activatePage(restored.tab, restored.filters);
}

State restoration (SaveStatedetails.currentState) is separate from the reload event, which carries Epic's navigation-history package. Do not use reload to restore saved state.

Navigation History

The library supports managing navigation history, tracking user interactions with Back/Forward buttons, and maintaining app-specific navigation states.

Listening to Navigation Events

Use onNavigate to track Back/Forward button clicks.

Example:

agl.on('navigate', ({ direction }) => {
   console.log(`Navigated: ${direction}`);
   // Handle navigation here
});

Saving Navigation History

Save custom navigation states using SaveHistoryState. AGL.js will persist these states for the current session.

Example:

agl.do('Epic.Clinical.Informatics.Web.SaveHistoryState', {
   state: JSON.stringify({ tab: 'dashboard', filters: { active: true } })
});

Disabling Navigation Buttons

Disable Back/Forward buttons in unsupported contexts.

Example:

agl.do('Epic.Clinical.Informatics.Web.SetEnabledHistBtns', {
   Back: false,
   Forward: false
});

Teardown

AGL.js installs one persistent message listener for the session. Call destroy() to remove it, settle any outstanding actions as false, and release the singleton. A later new AGL() then performs a fresh handshake.

Example:

agl.destroy();

The instance is also disposable, so using cleans it up automatically:

{
   using agl = new AGL();
   await agl.active;
   await agl.do('SaveState', { state: 'example' });
} // destroy() runs here

Migrating from 1.x

| Change | 1.x | 2.0 | |--------|-----|-----| | active | Returned boolean after init | Always returns Promise<boolean> — always await it | | reload payload | { state, fromHibernation } (unverified) | Raw historyPackage object | | State restore | Documented via reload | Read details.currentState after await agl.active | | CommonJS | require('agljs').default | require('agljs') | | Script-tag global | window.agl | window.AGL | | Print | (none) | onPrint / on('print', ...) | | Teardown | (none) | destroy() / [Symbol.dispose] | | Runtime | ES2022 | ES2024 with Symbol.dispose |

2.0 targets ES2024 and requires Symbol.dispose (modern Chromium/Edge).

Contributing

Fork, branch, and open a pull request.

Kopimi License License

Kopimi. Free to use, modify, and share. If the code is used, borrowed, modified, or incorporated into a commercial product or service, attribution is required.