Skip to content

The Grammar of Names ​

Before you skim the API alphabetically, one rule is enough — does the name carry attach?

Names without attach — they only compute ​

Functions like rsi(), macd(), bollingerBands() know nothing about the DOM or the canvas. They take data, compute values, return them. The return values (Rsi, Macd, …) draw nothing on screen — they are computed nodes, made to be read as numbers or fed into another computation. Headless testing is possible because of this half.

ts
import { rsi } from "@finchart/indicators";

const node = rsi(source, { period: 14 }); // values come out with no screen

Names with attach — they mount onto a pane ​

Functions carrying attach — attachRsi(), attachMacd(), attachBollingerBands() — take a Plot, mount a series onto a pane, wire up the axis and baselines, and hand back dispose(). Internally they call the bare function of the same name — attachRsi calls rsi() and lays pane wiring on top of it. That's why computation and rendering never sit mixed together in one function.

ts
import { attachRsi } from "@finchart/indicators";

const rsiPlugin = plot.use(attachRsi({ source, period: 14 })); // this one shows up

Where a bare name and an attach name come as a pair, bare only computes and attach is the one that puts it on screen — that naming rule is why plot.use(macd(...)) is rejected with a ContractError (use(plugin) wants a function, and a computed node is an object). The console already tells you the answer: follow the error message and you land on the missing attach*.

An aside: because of this rule, an alphabetical read of the API Reference bunches all 35 attach* functions up near A — a structural fact the naming rule produced, not a broken list.

An indicator's identifier is what the trading literature calls it, in camel case — the abbreviation when that is the name (macd, rsi, mfi, cci), the words when those are (ichimoku, momentum, elderRay, awesomeOscillator), and the mix when the literature mixes (parabolicSar, williamsR). The legend label is a different thing: the short form a chart legend shows, with the parameters attached — RSI(14), MTM(12,6), AO(5,34), Elder-Ray(13) Bull — so an identifier reads like the manual and a label like the screen.

The rest of the names are inferred the same way ​

attach/bare is the distinction you meet most often, but the whole public API follows the same principle (from the name alone you should be able to infer "what does it return, and where does it plug in").

  • Ingredients are nouns — plugin factories you plug into use, like crosshair()·tooltip()·legend()·drawingTools(); ingredients you plug into registration, like lineSeries()·priceLine(). Make one, plug it in, done. Computed nodes are nouns too — macd()·rsi() return a value (a node) and install nothing.
  • create* is the default-implementation factory for a contract — like createCanvasRenderer, it hides the implementation class and hands back the contract type.
  • State decides value or function — even among things that go into the same slot, stateless means a value (immediateScheduler·noStyle) and a parameter or per-instance state means a function (frameScheduler(view?)·manualScheduler(), which builds a fresh flush per instance). The split in call shape isn't asymmetry — it makes that difference visible; wrapping a stateless thing in a function would turn every call into a ceremony that rebuilds the same closure. Misuse is caught by the compiler.

One name that breaks the rule (kept as is): cssReader(container) in @finchart/dom returns a StyleReader (a contract), so it belongs in the create* slot, but it has no prefix. We decided not to rename it — createCssReader collides with an old slot name (PlotDeps.createCssReader, now createStyleReader), so satisfying one rule would leave the same name pointing at two different things in two eras. That cost outweighs rule purity. New names do follow the rule above.

Two packages: core and dom ​

@finchart/core and @finchart/dom are separate packages, not subpaths of one package — worth pinning down, because it's an easy import path to get wrong.

  • @finchart/core — all of the computation, the model, and the rendering contracts. Decorations and plugins that don't build DOM elements themselves, like crosshair(), live here too. Nothing here needs to know about the DOM, and that boundary is what makes headless testing and server rendering possible.
  • @finchart/dom — everything that builds and attaches DOM elements itself, like tooltip()·legend(), plus all of the browser wiring: browserDeps()·PlotBuilder·createDomLayers. You need a browser to use it.

The packages split this way because of a headless-first design — only the implementations that genuinely require the DOM (DOM layers, canvas renderer, text measurer, and so on) live in @finchart/dom; the rest stays open to testing or server rendering through the same entry point (@finchart/core) with no browser. The two packages ship together on one version — details are in the 60-second tutorial.

Serialized string unions ​

A string that gets saved — Drawing["type"], say — is a name you can never take back: it sits in a consumer's localStorage and their server long after a rename ships. Three rules, set before the drawing vocabulary grew:

  • A member names one tool, without repeating its type — horizontal, not horizontalLine; the union already says it's a drawing.
  • Don't reuse a word a public core type owns — Range belongs to the Range { min, max } type (the glossary pins that as the only non-pixel sense of the word), so a measuring tool is a measure, not a range.
  • After release, a member changes only through a format migration. Before release is the one free window — that's when verticalLine became vertical.