Published-release documentation · release compatibility
Action Authoring
The extension entry point is for developers adding traceable domain actions and
installing them on the complete chart() program.
Design an authoring action
Define the author’s decision before choosing the implementation’s method signature:
- Meaningfulness: choose a decision that an author would make on its own.
Prefer familiar operation–target names, such as
editLineMarkfor a line-style decision, over names describing internal arrays or incidental helper steps. - Design atomicity: keep independently meaningful decisions separate. One intent may require several coordinated changes: swapping axes can update two channels, their scales, and their guides. An unrelated title change need not be bundled into that action.
- Composition: reuse smaller actions that own meaningful parts of the decision. Promote a repeated, useful composition when it merits its own name; more names also increase the vocabulary an author must learn.
Choose the semantic owner and affected consumers before writing the action. Validate the proposed result, call the semantic primitive, and explicitly invoke the domain or graphical actions needed to rematerialize those consumers. Failure atomicity then ensures that an invalid request leaves the input program unchanged. It is a separate guarantee from design atomicity.
Register the implementation
Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Resolve these names from setup in this fragment or section; alternatives branch from the same base.
import { action, registerExtension } from "ggaction/extension";
Define each method with action(), then register the complete package in one
batch. Registration runs when the extension package is imported.
Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Resolve these names from setup in this fragment or section; alternatives branch from the same base.
const setPointOpacityAction = action(
{
op: "setPointOpacity",
description: "Set the opacity of a point mark."
},
function ({ target, value } = {}) {
return this.editGraphics({
target,
property: "opacity",
value
});
}
);
registerExtension({
name: "ggaction-example-extension",
actions: {
setPointOpacity: setPointOpacityAction
}
});
An application imports the extension for its registration side effect before creating or using a complete chart program.
Contextual fragment. Use an ES module with the imports, data, and prepared resource state described in this section. Resource selectors used here:
target: "points". Resolve these names from setup in this fragment or section; alternatives branch from the same base.
import { chart } from "ggaction";
import "ggaction-example-extension";
const program = chart()
.createCanvas()
.createGraphics({ id: "points", type: "circle", parent: "canvas" })
.setPointOpacity({ target: "points", value: 0.5 });
Registration affects chart() from ggaction, including programs created
before the import because methods live on the shared full-program prototype. It
does not add methods to ggaction/basic. An extension package must preserve its
registration module as a package side effect so bundlers do not remove it.
This registration example edits an existing primitive circle. It does not
create a semantic Point mark or define its position. For ordinary chart
appearance, use encodeOpacity on a semantic mark so later materialization
retains the requested opacity. Both language examples require the target first.
The entire action map is validated before any method is installed. Extension
names must be unique, action keys must match their wrapped op, and built-in,
internal, program-state, inherited, or previously registered names cannot be
overwritten. Non-conflicting packages work in either import order.
Strict TypeScript authoring
TypeScript cannot discover runtime registration alone. Augment
RegisteredExtensionActions in ggaction/extension with each exact wrapped
action type. This makes registered methods visible on ChartProgram without
duplicating their option signatures.
import { chart } from "ggaction";
import { action, registerExtension } from "ggaction/extension";
import type { FillPaint } from "ggaction/extension";
const extensionFill: FillPaint = {
type: "linear-gradient",
from: { x: 0, y: 0.5 },
to: { x: 1, y: 0.5 },
stops: [
{ offset: 0, color: "#eff6ff" },
{ offset: 1, color: "#1d4ed8" }
]
};
type SetPointOpacityOptions = Record<string, unknown> & {
target: string;
value: number;
};
const setPointOpacityAction = action<SetPointOpacityOptions>(
{
op: "setPointOpacity",
description: "Set the opacity of a point mark."
},
function ({ target, value }) {
return this.editGraphics({
target,
property: "opacity",
value
});
}
);
const markReadyAction = action(
{
op: "markReady",
description: "Record that extension authoring is complete."
},
function () {
return this;
}
);
declare module "ggaction/extension" {
interface RegisteredExtensionActions {
setPointOpacity: typeof setPointOpacityAction;
markReady: typeof markReadyAction;
}
}
registerExtension({
name: "ggaction-example-extension",
actions: {
setPointOpacity: setPointOpacityAction,
markReady: markReadyAction
}
});
export const extensionProgram = chart()
.createCanvas()
.createGraphics({ id: "points", type: "circle", parent: "canvas" })
.setPointOpacity({ target: "points", value: 0.5 })
.markReady();
export const extensionPaint = extensionFill;
This exact module is compiled in the installed-package test with strict: true,
NodeNext module resolution, and skipLibCheck: false. At runtime both methods
return ChartProgram; the trace contains setPointOpacity and markReady as
root actions, while the primitive calls remain children of setPointOpacity.
Isolated program classes
ChartProgram remains public for local or deliberately isolated program
classes. A subclass may assign wrapped actions to its own prototype. Installable
extension packages should use registerExtension() so multiple packages can
compose on the standard chart() factory with collision checks.
Action contract
- Metadata is snapshotted when
action()is called and requires a stable non-emptyopanddescription. - Every action accepts one plain option object.
- The implementation runs with the entered immutable program as
this. - It must return an instance of the same
ChartProgramclass. - Wrapped actions called inside it become trace children.
- Successful completion leaves the returned program’s action stack empty.
- The returned wrapped function preserves the concrete program class used as
this.
Arguments are summarized before storage in the trace. Arrays become counts, so large values are not retained twice. Circular plain-object arguments are rejected because a finite immutable trace summary cannot represent them.
Use the primitive extension API to express semantic and graphical changes. A semantic edit never materializes graphics automatically; the enclosing action must invoke every required graphical operation.
Installed guide
The installed package includes node_modules/ggaction/knowledge/extension-authoring.md.
Read it together with the installed types/extension.d.ts and types/program.d.ts
when extending an older release. This is a guide file, not a package export to import.
Documentation provenance
293 actions · sha256:e755591a2e5f5e2b494867583876e28020a478c5220b77bdc6ad469a6224891d