paratext/plugin
Every export of paratext/plugin, with its signature and doc comment: validate, register, reset, registered, contributions, attach and 2 more, plus 4 types.
The plugin host for paratext's half of the contract
(plugin-contract R1, R5a, R6, R7, R8).
A plugin is one plain object shared by the whole family. This file keeps the key paratext
understands — capabilities (R5a), OSC records by name — and ignores every other
key without complaining, which is what makes the same object work on any subset of the
family that is installed. A plugin written for flagstaff registers here and contributes
nothing; its tokens, spinners and components are not paratext's business and are not
an error.
Why paratext needed this file more than the other hosts did. Until it existed, paratext
was the one published package with no src/plugin.ts — and that file is the marker
scripts/plugin-schema-lock.test.ts and scripts/schema-sync.mjs both look for. So
paratext's copy of the family schema was outside the lock that keeps the copies
byte-identical: it happened to match flagstaff's, and nothing in the repository would have
said so if it stopped.
Nothing here imports another layer, and the plugin shape is declared rather than
imported (R3). It imports ./capability.js because that is this package's own module:
the capability shape, the registry and check() have to have exactly one home, or
"fallback is required" means one thing to a plugin and another to a built-in.
R7, and why this host is the easy case. R7 asks that no key require a function. Here
none does: a capability is { name, osc, when, encode, fallback }, five fields of plain
data with two template strings, so a plugin can arrive as JSON, be diffed, and be printed
by a plugin check without anybody running its author's code. closeout's handlers owes
R7 an exemption; capabilities owes it nothing, which is the argument the design makes
for holding capabilities as data in the first place.
import { validate, register, reset, … } from 'paratext/plugin';Functions
attach
Hand every contributed capability to a registry.
Defaults to paratext's own, which is what a program wants: attach() after importing the
plugins, and emit() can then reach them by name exactly as it reaches the built-ins.
Passing a host is for a caller keeping its own registry, and for a test that wants to see
the order without touching the global one.
function attach(host?: CapabilityHost): void;| Parameter | Type |
|---|---|
host (optional) | CapabilityHost |
Returns void
contributions
Every capability every registered plugin contributed, with who won each name.
This is the static projection of the plugin set (R7): a caller — or burgee plugin check
— reads what would be registered, and who lost, without registering anything and without
a terminal being involved.
function contributions(): Contribution[];Returns Contribution[]
register
Register a plugin. Later wins, like ESLint flat config: the array is ordered, a caller
reads it top to bottom, and the last word on a capability name is the one nearest the
program — which is also the registry's own rule, since register() in capability.ts
replaces by name so a caller can correct a guess we got wrong.
Registering does not emit, and does not even reach the capability registry. It records the contribution; {@link attach} is what hands it over. That separation is what makes {@link contributions} a static projection rather than a side effect.
function register(plugin: unknown): void;| Parameter | Type |
|---|---|
plugin | unknown |
Returns void
registered
The plugins registered, in registration order.
function registered(): readonly Plugin[];Returns readonly Plugin[]
reset
Forget every registered plugin. For tests, and for a program that re-plugs at runtime.
function reset(): void;Returns void
validate
Refuse a plugin that cannot contribute a capability, at the door.
Every refusal is a refusal rather than a silent drop. A capability quietly ignored looks like it worked right up until the day somebody runs the program on the terminal it was written for, and then the thing they debug is the terminal.
function validate(plugin: unknown): asserts plugin is Plugin;| Parameter | Type |
|---|---|
plugin | unknown |
Returns asserts plugin is Plugin
Classes
PluginError
A refused plugin says what is wrong and what to do about it — the family's one vocabulary.
class PluginError extends Error {
readonly code: PluginErrorCode;
readonly fix: string;
constructor(code: PluginErrorCode, message: string, fix: string);
}Constants
CONTRACT
The plugin contract version. One number for the family — the same 1 flagstaff, caique
and closeout declare, written out rather than imported for the reason in the file comment.
const CONTRACT = 1;Interfaces
CapabilityHost
The half of the capability registry this needs — declared structurally so a caller can attach to something it built itself, and so a host can watch what arrives.
interface CapabilityHost {
register(capability: Capability): void;
}Contribution
One contributed capability, with the plugin it came from and the plugins it displaced.
interface Contribution {
name: string;
from: string;
/** Plugins that contributed this name earlier and were overridden, in order. */
shadowed: string[];
capability: Capability;
}Plugin
The keys paratext reads. Declared structurally: any object with these fields is a plugin here, whatever else it carries.
interface Plugin {
name: string;
contract?: number;
/** Capabilities by name. The key and the record's own `name` must agree; see {@link validate}. */
capabilities?: Record<string, Capability>;
}Types
PluginErrorCode
Three codes, all of them already in flagstaff's PluginErrorCode — the vocabulary home
(scripts/plugin-error-vocabulary-lock.test.ts). E_NO_STATIC_PROJECTION is the one
worth noticing: flagstaff raises it for a component with no static form and caique for a
widget with no static, and a capability with no fallback is the same defect wearing
OSC. One code, one fix shape, three layers.
type PluginErrorCode = 'E_PLUGIN_SCHEMA' | 'E_PLUGIN_CONTRACT' | 'E_NO_STATIC_PROJECTION' | 'E_NO_CONTRIBUTION';