Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/quiet-machines-document.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@typeonce/effect-machine": patch
---

Document the nested configuration and callback APIs used by `Machine.states`, event protocols, `Machine.make`, `Definition.handle`, execution, transitions, and state invocation.

The documentation site now presents those core authoring APIs on a standalone guide-reference page while keeping module pages focused on public exports. Parameters remain grouped under their point of use with searchable signatures, lifecycle contexts, target semantics, defaults, source links, focused examples, and a complete nested page outline. Redundant configuration headings are omitted, and each parameter is presented as a named API block with its signature, description, and attached source link. Long signatures stay contained within their documentation blocks across responsive layouts.
140 changes: 140 additions & 0 deletions api-reference.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,146 @@
"export": "./Machine",
"source": "src/Machine.ts",
"barrel": ".",
"referenceSections": [
{
"title": "Define state topology",
"description": "Declare the complete state tree and its schema-backed values before constructing the machine.",
"entries": [
{ "declaration": "states" },
{ "declaration": "state" }
]
},
{
"title": "Define event protocols",
"description": "Describe public input, machine-local events, outward notifications, and parent ownership.",
"entries": [
{ "declaration": "events" },
{ "declaration": "internalEvents" },
{ "declaration": "emittedEvents" },
{ "declaration": "parent" },
{ "declaration": "optionalParent" }
]
},
{
"title": "Create the definition",
"description": "Combine topology and protocols into a reusable machine definition with an explicit initial target.",
"entries": [
{ "declaration": "make" }
]
},
{
"title": "Implement state behavior",
"description": "Attach state-local actions, transitions, output, and invoked lifecycles to the definition.",
"entries": [
{
"reflection": "Definition.handle",
"label": "handle",
"kind": "method",
"owner": "Definition",
"ownerKind": "interface",
"usageSections": [
"State handler configuration",
"Transition declarations",
"Handler and resolver contexts",
"State invocation"
]
}
]
},
{
"title": "Run and observe",
"description": "Start or resume an executable machine and subscribe to its state over time.",
"entries": [
{ "declaration": "start" },
{ "declaration": "resume" },
{ "declaration": "watch" }
]
}
],
"usageSections": [
{
"owner": "states",
"title": "State node configuration",
"description": "Properties accepted while declaring the state tree passed to `Machine.states`.",
"roots": [
{ "reflection": "Machine.AtomicStateNodeConfig", "label": "Atomic and final states" },
{ "reflection": "Machine.CompoundStateNodeConfig", "label": "Compound states" },
{ "reflection": "Machine.ParallelStateNodeConfig", "label": "Parallel states" },
{ "reflection": "Machine.HistoryStateNodeConfig", "label": "History states" },
{ "reflection": "Machine.ChoiceStateNodeConfig", "label": "Choice states" },
{ "reflection": "Machine.StateNodeAnnotations", "label": "Annotations" }
]
},
{
"owner": "make",
"title": "Machine definition configuration",
"description": "Parameters accepted by `Machine.make` and its initial-state declaration.",
"roots": [
{ "declaration": "make", "parameter": "config", "label": "Configuration" },
{ "reflection": "Machine.InitialTransitionTarget", "label": "Initial target resolution" }
]
},
{
"owner": "Definition",
"ownerKind": "interface",
"title": "State handler configuration",
"description": "Properties accepted for each state inside `Definition.handle`.",
"roots": [
{ "reflection": "Machine.ActiveStateConfig", "label": "Active states" },
{ "reflection": "Machine.FinalStateConfig", "label": "Final states" },
{ "reflection": "Machine.ChoiceStateConfig", "label": "Choice states", "members": ["choice"] },
{ "reflection": "Machine.HandlerNode", "label": "Nested handler tree", "members": ["states", "history"] },
{ "reflection": "Machine.OutputHandlerConfig", "label": "Output functions", "members": ["output"] },
{ "reflection": "Machine.HistoryDefaultEntry", "label": "History defaults" }
]
},
{
"owner": "Definition",
"ownerKind": "interface",
"title": "Transition declarations",
"description": "Target selection, resolution, branching, and commands available to state and invocation transitions.",
"roots": [
{ "reflection": "Machine.TargetSelector", "label": "Target selection" },
{ "reflection": "Machine.TargetBuilder", "label": "Resolver target construction" },
{ "reflection": "Machine.TransitionSelector", "label": "Named branch selection", "nested": true },
{ "reflection": "Machine.TransitionTarget", "label": "Selected target" },
{ "reflection": "Machine.TransitionBranchInput", "label": "Named branches" },
{ "reflection": "Machine.DeclineCapability", "label": "Declinable transitions" },
{ "reflection": "Enqueue", "label": "Enqueued commands" }
]
},
{
"owner": "Definition",
"ownerKind": "interface",
"title": "Handler and resolver contexts",
"description": "Values supplied to state actions, transitions, completion handlers, choices, and output functions.",
"roots": [
{ "reflection": "MachineReferences", "label": "Machine references" },
{ "reflection": "Machine.HandlerContext", "label": "Event handlers" },
{ "reflection": "Machine.StateActionContext", "label": "Entry and exit actions" },
{ "reflection": "Machine.AlwaysContext", "label": "Eventless transitions" },
{ "reflection": "Machine.DoneContext", "label": "State completion" },
{ "reflection": "Machine.ChoiceContext", "label": "Choice resolution" },
{ "reflection": "Machine.FinalOutputContext", "label": "Final output" },
{ "reflection": "Machine.ParallelOutputContext", "label": "Parallel output" }
]
},
{
"owner": "Definition",
"ownerKind": "interface",
"title": "State invocation",
"description": "State-owned Effect, Stream, timer, logic, and child lifecycles declared through `invoke`.",
"roots": [
{ "reflection": "Machine.InvokeSelector", "label": "Invocation sources" },
{ "reflection": "Machine.InvokeBuilder", "label": "Lifecycle handlers" },
{ "reflection": "Machine.InvokeContext", "label": "Source context" },
{ "reflection": "Machine.InvokeDoneContext", "label": "Completion context" },
{ "reflection": "Machine.InvokeFailureContext", "label": "Failure context" },
{ "reflection": "Machine.InvokeElementContext", "label": "Stream element context" },
{ "reflection": "Machine.InvokeSnapshotContext", "label": "Child snapshot context" }
]
}
],
"examples": [
"decodeSnapshot",
"encodeSnapshot",
Expand Down
153 changes: 153 additions & 0 deletions scripts/api-reference-site/api-reference-site.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
parseChangelog,
parseChangeset,
renderChangelogPage,
renderGuidePage,
renderIndexPage,
renderLayout,
renderMarkdown,
Expand Down Expand Up @@ -194,6 +195,156 @@ test("renders collapsible category navigation with declaration anchors", () => {
assert.match(html, /<h3 id="make">make<\/h3>/)
})

test("renders declaration-owned usage sections and documented members in the guide", () => {
const declaration = {
name: "make",
kind: "variable",
description: "Creates a machine.",
examples: [],
see: [],
usageSections: [{
owner: "make",
title: "Machine configuration",
description: "Parameters accepted by make.",
roots: [{
label: "Configuration",
description: "Complete machine configuration.",
examples: [],
members: [{
name: "id",
signature: "readonly id?: string",
description: "Stable definition identifier.",
sourceUrl: "https://github.com/example/repo/blob/main/src/Machine.ts#L1",
examples: [],
parameters: [],
members: []
}]
}]
}]
}
const module = {
api: {
declarationCount: 1,
description: "State machine APIs",
referenceSections: [{
title: "Create the definition",
description: "Construct the machine.",
entries: [{ origin: { type: "declaration", name: "make", kind: "variable" }, api: declaration }]
}],
groups: [{ category: "constructors", declarations: [declaration] }]
},
export: "./Machine",
label: "Machine",
route: "Machine"
}
const html = renderGuidePage({ ...site, modules: [module], guideModule: module })
assert.doesNotMatch(html, />Machine configuration<\//)
assert.match(html, /href="#make-usage-machine-configuration-configuration">Configuration<\/a>/)
assert.match(html, /<h4 id="make-usage-machine-configuration-configuration">Configuration<\/h4>/)
assert.match(html, /id="make-usage-machine-configuration-configuration-id" aria-labelledby="make-usage-machine-configuration-configuration-id-title"/)
assert.match(html, /<h5 id="make-usage-machine-configuration-configuration-id-title">id<\/h5>/)
assert.match(html, /<span class="syntax-keyword">readonly<\/span> id/)
assert.match(html, /<span class="syntax-type">string<\/span>/)
assert.match(html, /Stable definition identifier\./)
assert.doesNotMatch(html, />Usage reference</)
assert.ok(html.indexOf("id-title") < html.indexOf("Copy id signature"))
assert.ok(html.indexOf("Copy id signature") < html.indexOf("Stable definition identifier."))
assert.ok(html.indexOf(">Source</") < html.indexOf("Copy id signature"))
})

test("renders the ordered workflow only in the standalone guide", () => {
const states = {
name: "states",
kind: "variable",
description: "Defines state topology.",
examples: [],
see: [],
usageSections: []
}
const make = {
name: "make",
kind: "variable",
description: "Creates a definition.",
examples: [],
see: [],
usageSections: []
}
const handlerUsage = {
owner: "Definition",
ownerKind: "interface",
title: "State handler configuration",
description: "State-local behavior.",
roots: []
}
const definition = {
name: "Definition",
kind: "interface",
description: "Reusable definition model.",
examples: [],
see: [],
usageSections: [handlerUsage]
}
const helper = {
name: "helper",
kind: "function",
description: "Supporting helper.",
examples: [],
see: [],
usageSections: []
}
const handle = {
name: "handle",
kind: "method",
description: "Adds typed state handlers.",
examples: [],
see: [],
usageSections: [handlerUsage]
}
const module = {
api: {
declarationCount: 4,
description: "State machine APIs",
referenceSections: [{
title: "Define state topology",
description: "Start with the states.",
entries: [{ origin: { type: "declaration", name: "states", kind: "variable" }, api: states }]
}, {
title: "Create the definition",
description: "Construct and implement the machine.",
entries: [
{ origin: { type: "declaration", name: "make", kind: "variable" }, api: make },
{ origin: { type: "reflection", reflection: "Definition.handle" }, api: handle }
]
}],
groups: [
{ category: "constructors", declarations: [helper, make, states] },
{ category: "models", declarations: [definition] }
]
},
export: "./Machine",
label: "Machine",
route: "Machine"
}

const guideSite = { ...site, modules: [module], guideModule: module }
const guideHtml = renderGuidePage(guideSite)
assert.ok(guideHtml.indexOf("Define state topology") < guideHtml.indexOf('id="states"'))
assert.ok(guideHtml.indexOf('id="states"') < guideHtml.indexOf("Create the definition"))
assert.match(guideHtml, /<h3 id="handle">handle<\/h3>/)
assert.doesNotMatch(guideHtml, /id="helper"/)
assert.match(guideHtml, /class="navigation-guide is-current"/)
assert.ok(guideHtml.indexOf("navigation-changelog") < guideHtml.indexOf("navigation-guide is-current"))

const moduleHtml = renderModulePage(guideSite, module)
assert.match(moduleHtml, /<h3 id="helper">helper<\/h3>/)
assert.match(moduleHtml, /<h3 id="make">make<\/h3>/)
assert.match(moduleHtml, /<h3 id="states">states<\/h3>/)
assert.match(moduleHtml, /<h3 id="definition">Definition<\/h3>/)
assert.doesNotMatch(moduleHtml, /Define state topology/)
assert.doesNotMatch(moduleHtml, /State handler configuration/)
assert.doesNotMatch(moduleHtml, /id="handle"/)
})

test("renders canonical and social metadata without exposing the internal channel", () => {
const html = renderLayout(site, {
content: '<section class="reference-hero"><div class="eyebrow">API reference</div></section>',
Expand Down Expand Up @@ -264,11 +415,13 @@ test("keeps the internal Effect channel out of the homepage label", () => {

test("generates manifest, robots, and sitemap URLs from the deployment base", () => {
const manifest = siteManifest(site)
const siteWithGuide = { ...site, guideModule: site.modules[0] }
assert.equal(manifest.start_url, "/docs/")
assert.equal(manifest.icons[2].purpose, "maskable")
assert.match(renderRobots(site), /Sitemap: https:\/\/docs\.example\.com\/docs\/sitemap\.xml/)
assert.match(renderSitemap(site), /<loc>https:\/\/docs\.example\.com\/docs\/Machine\/<\/loc>/)
assert.match(renderSitemap(site), /<loc>https:\/\/docs\.example\.com\/docs\/changelog\/<\/loc>/)
assert.match(renderSitemap(siteWithGuide), /<loc>https:\/\/docs\.example\.com\/docs\/guide\/<\/loc>/)
})

test("accepts only pathless HTTPS production origins", () => {
Expand Down
Loading