---
id: "packages/active-helpers/api"
title: "API reference"
description: "Every export of active-helpers, per entry point - the package's own reference, and every export generated from the source."
url: "/packages/active-helpers/api"
locale: "en"
revision: "c01963d98cc17126"
---

# API reference

Three subpaths, no root entry point. Every module is dependency-free and runs
in any JavaScript runtime (browser, Node.js, worker). The source TSDoc is the
detailed contract; this page lists every export and what it guarantees.

## `./serialization`

Machine serialization for exports. Nothing here depends on a language: a
value is written the way a file or a clipboard needs it, not the way a reader
reads it.

| Export | Kind | Contract |
| --- | --- | --- |
| `neutralizeFormula` | function | `(text: string) => string`. Prefixes an apostrophe when a spreadsheet would otherwise execute the value: two or more characters starting with `=` or `@`, or with `+` or `-` followed by text, or by digits and an operator. A lone sign, and a signed plain number such as `-12`, stay unchanged. |
| `DEFAULT_CSV_DELIMITER` | constant | `','`: the RFC 4180 delimiter, used when a caller names none. |
| `csvField` | function | `(value: unknown) => string`. One CSV field: formula-neutralized and always quoted (embedded quotes doubled, newlines kept), so it is safe with any delimiter. `null` and `undefined` become empty text. |
| `csvRow` | function | `(values: readonly unknown[], delimiter?: string) => string`. The fields of `csvField` joined with `delimiter` (default `DEFAULT_CSV_DELIMITER`), verbatim: which delimiters a user may choose is the caller's policy. |
| `tsvField` | function | `(value: unknown) => string`. One TSV field: formula-neutralized, quoted only when a tab, newline or quote would break the structure; never stripped or replaced. |
| `tsvRow` | function | `(values: readonly unknown[]) => string`. The fields of `tsvField` joined with a tab. |
| `serializeDecimals` | function | `<T>(obj: T) => T`. Replaces every Decimal.js-style instance (an object with `toNumber()` and the fields `d`, `e` and `s`) by `toNumber()`, recursively through arrays and objects, so a query result crosses a server-to-client boundary. Lossy beyond double precision. `Date` and `RegExp` are returned unchanged. |

## `./export-fonts`

The backend-neutral mechanics of an embedded-font PDF export with a
jsPDF-class backend (no shaping, no bidi, UTF-16 code units). How a product
loads, parses and draws fonts stays with the product.

| Export | Kind | Contract |
| --- | --- | --- |
| `PdfFontBucket` | type | `'normal' \| 'bold' \| 'italic' \| 'bolditalic'`: the four styles such a backend registers per family. |
| `PDF_FONT_BUCKETS` | constant | Every bucket, regular first. |
| `pdfFontBucket` | function | The bucket of a CSS weight and style: weight 600 and up is bold. |
| `resolvePdfFontBucket` | function | `(available, requested)`: the bucket a family draws `requested` in: the exact bucket, else the same style in the other weight, else the same weight in the other style, else regular; `undefined` when the family has no regular face. Nothing is synthesised. |
| `FontByteFormat` | type | What the leading bytes of a font file say it is. |
| `classifyFontBytes` | function | `(bytes: Uint8Array) => FontByteFormat`. Classifies by signature without parsing. Only `truetype` is embeddable; CFF OpenType, WOFF, WOFF2 and collections are recognised so a refusal can name them. Fewer than twelve bytes is `unknown`. |
| `isWinAnsiCodePoint` | function | Whether a PDF standard font with WinAnsi encoding can draw a code point. |
| `isCollapsedWhitespace` | function | Tab, line feed and carriage return: collapsed to spaces, never drawn. |
| `PdfTextRefusalReason` | type | `'complex-script' \| 'combining-mark' \| 'joiner-or-selector' \| 'emoji' \| 'supplementary-plane'`. |
| `PdfTextRefusal` | type | The first code point a backend cannot draw, why, the script (for `complex-script`) and, for `combining-mark`, whether NFC would remove every mark. |
| `findPdfTextRefusal` | function | `(text: string) => PdfTextRefusal \| null`. The first code point that a backend without shaping cannot draw, or `null`. Never returns the text itself. |
| `PdfTextSegment` | type | One run of a text unit, drawn in one bucket. |
| `CoveringFamilyResult` | type | `{covered: true; index}` or `{covered: false; codePoint}`. |
| `selectCoveringFamily` | function | `(segments, families, covers)`: the first family, in the host's order, that covers every drawn code point of a unit in the bucket each segment is drawn in. One unit is drawn in one family; there is no per-glyph fallback. |

## `./menu-items`

The menu item contract and the one resolution every Active menu runs through
before it renders. How a menu renders, and what an action receives, stays
with the product.

| Export | Kind | Contract |
| --- | --- | --- |
| `MenuSeparator` | type | `'separator'`: a line between groups. |
| `MenuEntry<TArg, TIcon>` | interface | `label`, `action`, `icon`, `variant` (`'default' \| 'destructive'`), `disabled` with `disabledReason`, `shortcut`, `keepOpen`, `visible(arg)`, `separatorBefore`, `children`. |
| `MenuItem<TArg, TIcon>` | type | `MenuSeparator \| MenuEntry<TArg, TIcon>`. |
| `MenuEntryShape<TArg>` | interface | The part of an entry the pipeline reads; a product's own entry type satisfies it structurally. |
| `ResolveMenuOptions<TEntry, TArg>` | interface | `arg` (what `visible` is evaluated against; absent, `visible` is not evaluated) and `keep(entry)` (a product's own filter, asked first). |
| `normalizeMenuSeparators` | function | Drops separators that no longer separate anything: no leading, trailing or doubled line. Order is preserved. |
| `ensureDestructiveTailSeparator` | function | When a menu ends on destructive entries, puts one line before that tail (within the last segment). Idempotent. |
| `resolveMenuItems` | function | The pipeline, at every submenu depth: filter (`keep`, then `visible`), expand `separatorBefore` into lines, give the destructive tail its line, clean up the lines. Idempotent. A submenu whose children all filter away keeps its trigger with an empty `children`. |

## All exports, from the source

### `@workspace/active-helpers/serialization`

**csvField**: `(value: unknown) => string`

Render one CSV field. Always quoted, header fields included, so a header
containing the delimiter cannot produce more columns than the data rows. Embedded quotes are doubled
per RFC 4180; embedded newlines are legal inside a quoted field and are kept
as-is, so multi-line cell content survives the round trip.

Quoting is UNCONDITIONAL and therefore delimiter-independent: a field that
contains the separator, a quote or a newline is already safe whichever
separator the row ends up joined with. That is why this function takes no
delimiter — there is no decision here that depends on one.

**csvRow**: `(values: readonly unknown[], delimiter?: string) => string`

Render one CSV row from its fields.

`delimiter` is the separator between fields and defaults to a comma. It is
joined verbatim: the
caller owns which separators it permits, because "which delimiter may a user
choose" is a product question and this is the serialisation boundary. What
the boundary DOES guarantee is that any separator is safe — see
{@link csvField} on unconditional quoting.

**DEFAULT_CSV_DELIMITER**: `","`

The delimiter a CSV row uses when the caller names none: RFC 4180's comma.

**neutralizeFormula**: `(text: string) => string`

Prefix a leading apostrophe when a spreadsheet would otherwise execute the
value. Measured: the apostrophe is NOT consumed on either the CSV or the
paste route, so it stays visible in the cell. That is accepted — it reads as
a deliberate marker, where a leading space would read as a defect.

**tsvField**: `(value: unknown) => string`

Render one clipboard TSV field.

Quoting on the paste route was MEASURED to be honoured by Excel and to be
lossless: `"mid<TAB>dle"` arrives as one cell containing the tab, a quoted
newline stays inside its cell, and `"plain"` arrives without its quotes.
That is why this does not strip or replace anything: collapsing tab, CR or
LF to a space would be a silent, lossy data change.

Quoting is MINIMAL: only fields that would otherwise break the structure are
quoted, so plain values stay plain when pasted into a text editor. A field
containing a `"` is quoted too — an unquoted field starting with a quote was
measured to swallow the following delimiter and merge two columns.

**tsvRow**: `(values: readonly unknown[]) => string`

Render one clipboard TSV row from its fields.

**serializeDecimals**: `<T>(obj: T) => T`

Recursively convert decimal.js instances (Prisma `Decimal`) to plain
numbers, so a query result can cross the server-to-client boundary, which
does not serialize class instances. The conversion is `toNumber()` and
therefore lossy beyond double precision; a caller that needs exact digits
must convert to a string itself.

Arrays and plain objects are rebuilt recursively (a `__proto__` key stays an
own property); `Date` and `RegExp` instances are returned unchanged.

### `@workspace/active-helpers/export-fonts`

**PDF_FONT_BUCKETS**: `readonly PdfFontBucket[]`

Every bucket, regular first.

**pdfFontBucket**: `(weight: number, style: "normal" \| "italic") => PdfFontBucket`

The bucket of a CSS weight and style: weight 600 and up is bold, the rest
normal (the rule the charts' PDF export has kept since FAM-V2-T6).

**resolvePdfFontBucket**: `(available: ReadonlySet<PdfFontBucket>, requested: PdfFontBucket) => PdfFontBucket \| undefined`

The bucket a family draws `requested` in, given the buckets it has:
the exact bucket, else the same style in the other weight, else the same
weight in the other style, else regular. Nothing is synthesised: a family
without bold draws bold text in its regular face. `undefined` only when
the family has no regular face either.

**classifyFontBytes**: `(bytes: Uint8Array) => FontByteFormat`

Classifies font bytes by their signature, without parsing them. Only
`truetype` (`00 01 00 00` or `true`) is embeddable by a jsPDF-class
backend; CFF-flavoured OpenType (`OTTO`), WOFF, WOFF2 and collections
(`ttcf`) are recognised so a refusal can name them. Fewer than twelve
bytes (an sfnt header) is `unknown`.

**isWinAnsiCodePoint**: `(codePoint: number) => boolean`

Whether a PDF standard font (Helvetica, Times, Courier with WinAnsi
encoding) can draw `codePoint`: printable ASCII, U+00A0–U+00FF and the
twenty-seven Windows-1252 additions (€, curly quotes, dashes, …).

**isCollapsedWhitespace**: `(codePoint: number) => boolean`

Tab, line feed and carriage return: SVG and table text collapses them to
spaces, so they are never drawn and need no glyph.

**findPdfTextRefusal**: `(text: string) => PdfTextRefusal \| null`

The first code point of `text` that a PDF backend without shaping, bidi
and supplementary-plane encoding cannot draw correctly, or `null`.

Refused, in this order per code point: emoji (default emoji presentation,
or a supplementary-plane pictograph; a text-presentation pictograph such as
©, ® or ™ stays text), joiners and variation selectors (ZWNJ, ZWJ,
VS1–VS256), a complex or right-to-left script (the list above, and
conjoining Hangul jamo), any combining mark, and any code point beyond the
Basic Multilingual Plane. Everything else is left to glyph coverage.

The analysis reads the text as it will be drawn. NFC is used only to tell
the host whether a refused combining mark would compose away.

**selectCoveringFamily**: `<F>(segments: readonly PdfTextSegment[], families: readonly F[], covers: (family: F, bucket: PdfFontBucket, codePoint: number) => boolean) => CoveringFamilyResult`

The first family, in the host's order, that covers EVERY drawn code point
of a text unit in the bucket each segment is drawn in. One unit is drawn
in one family: there is no per-glyph fallback, so a unit that mixes
scripts needs one family that covers all of them.

`covers(family, bucket, codePoint)` answers for the face the family draws
`bucket` in. Collapsed whitespace (tab, LF, CR) needs no glyph.

**PdfFontBucket**

```ts
type PdfFontBucket = 'normal' | 'bold' | 'italic' | 'bolditalic';
```

The four styles a jsPDF-class backend registers per family.

**FontByteFormat**

```ts
type FontByteFormat =
  'truetype' | 'cff' | 'woff' | 'woff2' | 'collection' | 'unknown';
```

What the leading bytes of a font file say it is.

**PdfTextRefusalReason**

```ts
type PdfTextRefusalReason =
  | 'complex-script'
  | 'combining-mark'
  | 'joiner-or-selector'
  | 'emoji'
  | 'supplementary-plane';
```

Why a PDF backend without shaping cannot draw a text.

**PdfTextRefusal**

```ts
interface PdfTextRefusal {
  reason: PdfTextRefusalReason;
  codePoint: number;
  script?: string;
  normalizable?: boolean;
}
```

The first code point of a text that a PDF backend cannot draw, and why.

**PdfTextSegment**

```ts
interface PdfTextSegment {
  text: string;
  bucket: PdfFontBucket;
}
```

One run of a text unit, drawn in one requested bucket.

**CoveringFamilyResult**

```ts
type CoveringFamilyResult =
  | {covered: true; index: number}
  | {
      covered: false;
      codePoint: number;
    };
```

The outcome of {@link selectCoveringFamily}.

### `@workspace/active-helpers/menu-items`

**normalizeMenuSeparators**: `<TEntry>(items: ReadonlyArray<MenuSeparator \| TEntry>) => Array<MenuSeparator \| TEntry>`

Drop separators that no longer separate anything: no leading separator, no
trailing separator, never two in a row.

A caller declares separators between groups, then filtering removes the
entries a viewer may not see or that are not relevant; without this, the
menus seen least often open on a rule, end on one, or show two with nothing
between them. Order is preserved; only separators are removed.

**ensureDestructiveTailSeparator**: `<TEntry extends { variant?: "default" \| "destructive"; }>(items: ReadonlyArray<MenuSeparator \| TEntry>) => Array<MenuSeparator \| TEntry>`

THE DESTRUCTIVE TAIL GETS ITS SEPARATOR, even without a declaration.

A menu that ends in destructive entries is separated from the ordinary
entries above by one line. Only within the last segment (after the last
line the caller set), the tail is the run from the FIRST destructive entry,
provided the menu ends on a destructive entry: so a mixed lifecycle group
(`Delete` → `Restore` → `Delete permanently`) stays one group.

It does nothing when the menu does not end on a destructive entry (only
the caller knows that boundary: `separatorBefore`), when the segment
already begins at the tail, or when nothing stands before the tail. It only
adds a line, and running it twice changes nothing further.

**resolveMenuItems**: `<TEntry extends MenuEntryShape<TArg>, TArg>(items: ReadonlyArray<MenuSeparator \| TEntry>, options?: ResolveMenuOptions<TEntry, TArg>) => Array<MenuSeparator \| TEntry>`

THE ONE PIPELINE. Four steps, in this order, at every submenu depth:

1. **filter**: the product's `keep`, then `visible` against `arg`;
2. **expand `separatorBefore`** into lines;
3. **give the destructive tail** its line;
4. **clean up**: no leading, trailing or doubled line.

Filtering comes first because filtering orphans lines. A submenu whose
children all filter away keeps its trigger with an empty `children`; a
renderer renders an entry with no children as an ordinary entry.

It is idempotent, so a product may run it in a factory and again at its
renderer boundary over whatever it receives.

**MenuSeparator**

```ts
type MenuSeparator = 'separator';
```

A separator line in a menu.

**MenuEntry**

```ts
interface MenuEntry<TArg, TIcon = unknown> {
  label: string;
  action?: (arg: TArg) => void;
  icon?: TIcon;
  variant?: 'default' | 'destructive';
  disabled?: boolean;
  disabledReason?: string;
  shortcut?: string;
  keepOpen?: boolean;
  visible?: (arg: TArg) => boolean;
  separatorBefore?: boolean;
  children?: MenuItem<TArg, TIcon>[];
}
```

The fields every Active menu entry shares, with the same meaning in every
product. `TArg` is what the product's actions and predicates receive (a
grid row, a chart menu context); `TIcon` the product's icon type.

**MenuItem**

```ts
type MenuItem<TArg, TIcon = unknown> =
  MenuSeparator | MenuEntry<TArg, TIcon>;
```

One item of an Active menu.

**MenuEntryShape**

```ts
interface MenuEntryShape<TArg> {
  variant?: 'default' | 'destructive';
  visible?: (arg: TArg) => boolean;
  separatorBefore?: boolean;
  children?: ReadonlyArray<MenuSeparator | MenuEntryShape<TArg>>;
}
```

The part of an entry the pipeline reads. A product's own entry type (with
its extra fields) satisfies it structurally.

**ResolveMenuOptions**

```ts
interface ResolveMenuOptions<TEntry, TArg> {
  arg?: TArg | null;
  keep?: (entry: TEntry) => boolean;
}
```
