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:

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

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