# apex-commons

> Shared utilities for ApexCharts products (licensing, watermark, motion).

Latest version **0.8.1** (published 2026-09-15) · SEE LICENSE IN LICENSE license · 0 weekly downloads

## Install

```sh
npm install apex-commons
pnpm add apex-commons
yarn add apex-commons
bun add apex-commons
```

## Health

**Score 65/100 (B)** — status: active.

Positive: has types; esm support; no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; pre 1.0.

## Facts

| | |
|---|---|
| Version | 0.8.1 |
| Published | 2026-09-15 |
| First published | 2026-06-04 |
| Weekly downloads | 0 |
| License | SEE LICENSE IN LICENSE |
| TypeScript types | bundled |
| Module format | ESM + CommonJS |
| Dependencies | 0 |
| Unpacked size | 174.5 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | junedchhipa |

## Links

- npm: https://www.npmjs.com/package/apex-commons
- Repository: https://github.com/apexcharts/projects
- Homepage: https://github.com/apexcharts/projects#readme
- Issues: https://github.com/apexcharts/projects/issues
- npm.io page: https://npm.io/package/apex-commons

## Recent versions

- 0.8.1 (latest) — 2026-09-15
- 0.8.0 — 2026-09-15
- 0.7.0 — 2026-09-10
- 0.6.0 — 2026-09-07
- 0.5.0 — 2026-08-13
- 0.4.0 — 2026-08-12
- 0.3.0 — 2026-08-11
- 0.2.0 — 2026-07-27
- 0.1.0 — 2026-06-04

## README

# apex-commons

The shared core behind the ApexCharts product family: licensing, watermarking, theming, internationalisation, spring motion and coordinated filtering.

It exists because these things are not chart-specific. A licence key, a theme token, a text direction and a filter mean the same thing to a chart, a tree, a sankey diagram and a grid, and each of those having its own copy meant four implementations drifting apart. Anything the family needs to agree on lives here, so it is agreed on once.

Most users never install this directly. It arrives as a dependency of the product you are using, and the parts you touch are reached through that product's own API. Install it yourself when you are building against several of them at once, or using the crossfilter engine with no chart present.

## Installation

```bash
npm install apex-commons
```

## Licensing

`LicenseManager` is the enforcement path for the whole family, so a key set once here is honoured by every Apex product on the page.

```js
import {LicenseManager} from 'apex-commons';

LicenseManager.setLicense('your-license-key');
```

Without a valid key a product keeps working and carries a trial watermark. That is deterrence and honest-customer compliance rather than DRM: no feature is ever degraded.

The products this serves are free under the Community terms for organisations under the revenue threshold, and need a Commercial licence otherwise. See <https://apexcharts.com/pricing>.

| Member                    | Description                                                              |
| ------------------------- | ------------------------------------------------------------------------ |
| `LicenseManager`          | Static class holding the licence for every Apex product on the page.     |
| `setLicense`              | Set the key. Call before creating any instance.                          |
| `getKey`                  | The key currently in force, or `null`.                                   |
| `isLicenseValid`          | Whether the key in force is valid right now.                             |
| `isKeyValid`              | Whether a given key would be valid, without setting it.                  |
| `getLicenseStatus`        | The full `LicenseValidationResult` for the key in force.                 |
| `validateKey`             | Validate a given key and return its result, without setting it.          |
| `onChange`                | Subscribe to licence changes. Returns an unsubscribe function.           |
| `LicenseData`             | The decoded payload: plan, issue and expiry dates, optional domains.     |
| `LicenseValidationResult` | The outcome of a check: whether it is valid, and why not when it is not. |
| `LicenseChangeListener`   | The callback shape `onChange` takes.                                     |

### Plan tiers

Whether a licence covers a feature, in one place, so every Apex product answers it the same way.

`planAtLeast` has one hard boundary and one deliberate softness. Without a valid licence it always returns `false`. With a valid licence whose plan string is not recognised it returns `true` and warns once, because the holder has a signed, unexpired key: refusing them a feature over a string we failed to map is worse than granting one. Gating here is deterrence and honest-customer compliance, not DRM.

```js
import {planAtLeast} from 'apex-commons';

if (planAtLeast('premium')) {
  // show the tool
}
```

| Member          | Description                                                                            |
| --------------- | -------------------------------------------------------------------------------------- |
| `planAtLeast`   | Whether the licence in force covers a feature needing at least the given plan.         |
| `currentPlan`   | The plan the licence grants, or `null`. For display; use `planAtLeast` to gate.        |
| `normalisePlan` | Resolve a raw plan string to a known plan, ignoring case and space. `null` if unknown. |
| `PLAN_ORDER`    | Every plan string a key may carry, in commercial ladder order. Not the gating order.   |
| `PLAN_ALIASES`  | Plan strings that may appear in issued keys but are not current plan names.            |
| `ApexPlan`      | The plan names: `community`, `pro`, `premium`, `embedded`, `oem`.                      |

**There are three gating levels, not five.** `community` < `pro` < `premium`, and `embedded` and `oem` rank **equal to** `premium` rather than above it. Those two sell redistribution rights and, in the words of the licence they are sold under, not usage rights: what an OEM customer's runtime may do is decided by the per-developer licence bought alongside, which is what the key carries. So `premium` is the ceiling, and nothing should gate above it. A feature that did would be reachable by no purchasable licence at all.

Both strings stay in the vocabulary rather than being removed, because `planAtLeast` fails open on a plan it does not recognise: drop them and a key carrying one would be granted everything, which is the opposite of the intent.

`community` never appears in a key, because that tier is issued none, so `planAtLeast('community')` means "holds any licence at all".

`oem` was called `enterprise` until 2026-09-14, and `PLAN_ALIASES` maps the old string so a key carrying it still resolves to a real rank rather than fail-opening.

### Watermark

The trial mark an unlicensed product adds to its container.

| Member             | Description                                                  |
| ------------------ | ------------------------------------------------------------ |
| `Watermark`        | Static class that adds, finds and removes the trial mark.    |
| `WatermarkOptions` | Options accepted by `add` and `remove`.                      |
| `ATTR`             | The attribute the mark carries, for finding it.              |
| `add`              | Add the mark to a container. Returns the element, or `null`. |
| `remove`           | Remove the mark from a container.                            |
| `exists`           | Whether a container currently carries a mark.                |
| `node`             | The mark element inside a container, or `null`.              |
| `applyStyles`      | Apply the mark's styles to an element.                       |
| `untrack`          | Stop tracking a container, without removing its mark.        |

## Internationalisation

| Member            | Description                                                                |
| ----------------- | -------------------------------------------------------------------------- |
| `TextDirection`   | `'ltr'`, `'rtl'` or `'auto'`.                                              |
| `resolveMessages` | Merge a caller's partial message overrides onto a product's defaults.      |
| `applyDirection`  | Set text direction on an element, resolving `'auto'` against the document. |
| `isRTL`           | Whether a direction resolves to right-to-left, given an optional element.  |

## Theming

A page declares the `--apx-*` custom properties once on `:root` and every Apex product on it follows. Anything set explicitly on a product wins, so a product already themed through its own options is untouched.

| Member | Description |
| --- | --- |
| `ApxTokens` | The resolved token set: accent, foreground, grid, surface and the series palette. |
| `APX_TOKEN_VARS` | The custom property name for each token role. |
| `APX_SERIES_VAR_PREFIX` | Prefix for the ordered series palette, `--apx-series-N`. |
| `readTokens` | Read the tokens currently in force on an element. |
| `resolveTokenLayer` | Read the tokens for an element, with a registered theme underneath. |
| `pickToken` | Resolve one value through the precedence order: option, then CSS variable, then token, then fallback. |
| `TokenPrecedenceInput` | The candidates `pickToken` chooses between. |
| `ApxTheme` | A named theme: the token roles it contributes. |
| `registerTheme` | Register or override a named theme on the shared registry. |
| `getTheme` | Look up a registered theme by name. |
| `registeredThemes` | The names currently registered. |
| `unregisterTheme` | Remove a registered theme. |
| `OwnedToken` | Tracks a custom property a product set itself, so it can put back what it found. |
| `OSThemeWatcher` | Watches the operating system's light and dark preference. |
| `OSThemeState` | The current OS preference. |
| `OSThemeWatcherOptions` | Options for the watcher. |

## Motion

A headless spring driver, shared so that motion feels the same across the family. It computes values and holds no opinion about what draws them.

| Member                        | Description                                                                   |
| ----------------------------- | ----------------------------------------------------------------------------- |
| `Spring`                      | One spring: its current value, velocity, target and rest thresholds.          |
| `makeSpring`                  | Create a spring at a value, with a stiffness and damping.                     |
| `stepSpring`                  | Advance a spring by a timestep. Returns whether it is still moving.           |
| `retarget`                    | Point a spring at a new target, keeping its velocity.                         |
| `snapSpring`                  | Move a spring to a value immediately, clearing velocity.                      |
| `SpringRest`                  | The velocity and displacement below which a spring counts as arrived.         |
| `UNIT_REST`                   | Rest thresholds for springs running over a 0 to 1 range, rather than pixels.  |
| `SpringPreset`                | `'crisp'`, `'gentle'` or `'snappy'`.                                          |
| `SPRING_PRESETS`              | The stiffness and damping pair behind each preset.                            |
| `resolveSpring`               | The pair for a preset, falling back to the default.                           |
| `CAMERA_SPRING`               | The pair used for pan and zoom, which wants less overshoot than content.      |
| `WAVE_STAGGER_MS`             | Delay between neighbours in a staggered entrance.                             |
| `SpringScene`                 | Drives many named springs on one animation loop, with a single rest callback. |
| `SpringSceneOptions`          | Options for a scene, including its clock.                                     |
| `SpringValues`                | The current value of every spring in a scene, by name.                        |
| `SceneObjectOptions`          | Options for one object added to a scene.                                      |
| `SceneClock`                  | The scene's time source. Substitutable, so tests need no timers.              |
| `diffNodes`                   | Split previous and next id sets into entering, surviving and leaving.         |
| `NodeDiff`                    | The result of `diffNodes`.                                                    |
| `ringDistances`               | Distance of each node from a set of roots, for staggering a wave outwards.    |
| `ReducedMotionWatcher`        | Watches the user's reduced-motion preference.                                 |
| `ReducedMotionWatcherOptions` | Options for the watcher.                                                      |

## Crossfilter

Coordinated filtering across any number of views. One shared record set, and per view a dimension (`row => key`), a reduction and a filter.

A view's aggregation is computed over the records passing every **other** view's filter, never its own, so each view keeps showing what else is reachable instead of collapsing to its own selection. A view's own filter only marks which of its buckets are selected.

Because a dimension is an arbitrary function, a map region, a tree subtree, a sankey link, a grid column and a chart category are all the same thing to it, and a set of views can coordinate with no chart present at all.

```js
import {Crossfilter} from 'apex-commons';

const cf = Crossfilter.getOrCreate({id: 'audit'});
cf.setRecords(rows);
cf.registerDimension('byRegion', {dimension: (r) => r.region});
cf.on('change', () => render(cf.aggregateAll()));
cf.filter('byRegion', ['EU']);
```

| Member              | Description                                                        |
| ------------------- | ------------------------------------------------------------------ |
| `Crossfilter`       | The coordinator.                                                   |
| `getOrCreate`       | Static. The coordinator for an id, creating it if needed.          |
| `get`               | Static. The coordinator for an id, or `null`.                      |
| `setRecords`        | Replace the shared record set.                                     |
| `registerDimension` | Add or replace a view's dimension and reduction.                   |
| `hasDimension`      | Whether a view is registered.                                      |
| `removeDimension`   | Remove a view.                                                     |
| `filter`            | Set a view's filter.                                               |
| `toggleKey`         | Add or remove one key from a view's filter.                        |
| `filterOf`          | A view's current filter, or `null`.                                |
| `clear`             | Clear one view's filter.                                           |
| `reset`             | Clear every filter.                                                |
| `aggregateFor`      | The aggregation for one view.                                      |
| `aggregateAll`      | The aggregation for every registered view.                         |
| `filteredRecords`   | Records passing every filter, optionally excluding one view's own. |
| `filteredRows`      | Records passing every filter.                                      |
| `state`             | A snapshot of records, filters and aggregations.                   |
| `on`                | Subscribe to an event. Returns an unsubscribe function.            |
| `off`               | Remove a handler.                                                  |
| `dataTable`         | Render a paged table of the filtered rows into an element.         |

### Crossfilter types

| Type                  | Description                                                   |
| --------------------- | ------------------------------------------------------------- |
| `CrossfilterOptions`  | Options for `getOrCreate`, including the shared `id`.         |
| `CrossfilterRow`      | One record. Shape is entirely the caller's business.          |
| `CrossfilterState`    | The snapshot `state()` returns.                               |
| `CrossfilterEvent`    | The events a coordinator emits.                               |
| `CrossfilterListener` | The callback shape `on` takes.                                |
| `DimensionSpec`       | A view's dimension, reduction, ordering and binning.          |
| `DimensionAccessor`   | Maps a row to this view's key.                                |
| `DimensionType`       | Whether a dimension is categorical or numeric.                |
| `DimensionOrder`      | How a view's buckets are ordered.                             |
| `BinSpec`             | How a numeric dimension is bucketed.                          |
| `ReduceSpec`          | How rows in a bucket become a scalar.                         |
| `Reducer`             | A custom reduction function.                                  |
| `FilterInput`         | What `filter` accepts: a set of keys, or a numeric range.     |
| `Aggregate`           | Union of the aggregation shapes. Narrow it before reading.    |
| `CategoryAggregate`   | A flat list of keys and values.                               |
| `RangeAggregate`      | A numeric range aggregation.                                  |
| `MatrixAggregate`     | A two-dimensional aggregation, which has no flat `keys` list. |
| `DataTableOptions`    | Options for `dataTable`.                                      |
| `DataTableColumn`     | One column in a data table.                                   |
| `DataTableHandle`     | The handle `dataTable` returns.                               |

## Indicators

Moving-window indicators as pure functions, lifted out of apexstock so the chart family and the stock product share one implementation. Each takes `Array<number | null>` and returns the same length, with `null` wherever a value is not defined.

A window containing a gap yields `null` rather than an average of however many values were present, which would quietly compute a mean of four and show it beside means of ten.

`sma` is slice-invariant: a window averages to the same value whatever precedes it, so `sma(values.slice(i - period + 1, i + 1), period)` at its last position equals `sma(values, period)[i]` exactly. That is what lets a streaming consumer extend a series by recomputing only the tail window. It costs a running total, so `sma` is O(n \* period).

| Member | Description |
| --- | --- |
| `sma` | Trailing simple moving average, aligned to the last position in each window. Slice-invariant, as above. |
| `ema` | Exponential moving average, seeded from the first complete simple window rather than the first value. A gap ends the run and the next complete window starts a new one. |
| `bollinger` | A simple moving average with a moving standard deviation either side, using the population denominator, which is the convention for this band. |
| `Bands` | What `bollinger` returns: `middle`, `upper` and `lower`, each the length of the input. |

```js
import {bollinger, ema, sma} from '@apex/commons';

sma([1, 2, 3, 4, 5, 6], 3); // [null, null, 2, 3, 4, 5]
ema([1, 2, 3, 4, 5], 3); // [null, null, 2, 3, 4]
bollinger([2, 4, 4], 3, 2).upper;
```

## Histogram binning

Observations in, bin edges and counts out. Pure arithmetic, so it is testable on its own and inert under SSR.

Gaps mean the opposite of what they mean above. A moving average over a window containing a gap is undefined, because the window is a claim about a stretch of the series. A histogram is a tally of the observations that exist, so a gap is simply not one; `histogram` skips them and reports how many it skipped.

| Member | Description |
| --- | --- |
| `histogram` | Bin a series and count it in one call, tolerating gaps. The convenience most callers want. |
| `computeBinning` | Choose bin edges for a set of finite observations. Precedence: an explicit `binWidth`, then an explicit bin count, then a named rule. |
| `binCounts` | Count observations into a set of edges. |
| `binIndexOf` | The bin holding one value, or `-1` outside the edges. Bins are half-open except the last, which includes its upper edge. |
| `quantileSorted` | Quantile of an ascending-sorted array, by the "type 7" definition that R, NumPy and a spreadsheet's QUARTILE all share. |
| `Histogram` | What `histogram` returns: a `Binning` plus `counts`, `midpoints`, `total` and `missing`. |
| `Binning` | `edges`, `binWidth`, the `rule` that actually chose the width, and whether the count was `capped`. |
| `BinOptions` | `bins` (a rule name or a fixed count), `binWidth`, and a `range` to frame the bins on so several histograms share a scale. |
| `BinRule` | The named rules: `auto`, `fd`, `rice`, `scott`, `sqrt`, `sturges`. `auto` is the narrower of `fd` and `sturges`, falling back to `sturges` when the IQR is zero. |

```js
import {histogram} from '@apex/commons';

const h = histogram([1, 2, 2, 3, null, 9]);
h.counts; // observations per bin
h.missing; // 1
```

## Browser support

Every module is SSR-safe: nothing touches `document` at import time, and the pieces that need the DOM take the element they act on. `Crossfilter` is DOM-free apart from its optional `dataTable` helper.

---
_Source: https://npm.io/package/apex-commons · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
