Contributing and Support
API Reference
Programmatic API reference for using Spry as a library
Overview
Spry can be embedded in TypeScript/JavaScript applications using its core APIs:
// Main imports
import { markdownASTs } from "./lib/axiom/io/mod.ts";
import { graph } from "./lib/axiom/graph.ts";
import { executionPlan } from "./lib/universal/task.ts";
import { playbooksFromFiles } from "./lib/axiom/projection/playbook.ts";markdownASTs
Parse Markdown files into AST representations.
Signature
async function* markdownASTs<P, S>(
provenances: readonly string[] | Iterable<P> | AsyncIterable<P>,
options?: MarkdownASTsOptions<P, S>
): AsyncGenerator<MarkdownEncountered>Usage
import { markdownASTs } from "./lib/axiom/io/mod.ts";
// Process single file
for await (const md of markdownASTs(["./Spryfile.md"])) {
console.log("File:", md.file.path);
console.log("AST:", md.mdastRoot);
}// Process multiple files
for await (const md of markdownASTs(["./doc1.md", "./doc2.md"])) {
// Process each document
}// Process remote files
for await (const md of markdownASTs(["https://example.com/doc.md"])) {
// Process remote document
}Return Type
Each yielded item contains:
| Property | Type | Description |
|---|---|---|
resource | VFileResource | Source resource |
file | VFile | Virtual file object |
mdastRoot | Root | Parsed AST root |
nodeSrcText | object | Source text helpers |
mdSrcText | string | Raw Markdown text |
fileRef | function | File reference formatter |
relativeTo | function | Path resolver |
Options
interface MarkdownASTsOptions<P, S> {
pipeline?: ReturnType<typeof mardownParserPipeline>;
factory?: ReturnType<typeof vfileResourcesFactory<P, S>>;
}Graph API
Build semantic graphs from parsed ASTs.
graph()
Create a graph from an mdast root:
import { graph } from "./lib/axiom/graph.ts";
for await (const md of markdownASTs(["./doc.md"])) {
const g = graph(md.mdastRoot);
console.log("Relationships:", g.rels);
console.log("Edge counts:", g.relCounts);
console.log("Edges:", g.edges);
}Return Type
interface Graph<Relationship, Edge> {
root: Root;
edges: readonly Edge[];
rels: Set<string>;
relCounts: Record<string, number>;
}graphToDot()
Export graph to Graphviz DOT format:
import { graphToDot } from "./lib/axiom/graph.ts";
const dot = graphToDot(g, { graphName: "MyGraph" });
await Deno.writeTextFile("graph.dot", dot);Projection APIs
Transform graphs into domain-specific models.
flexibleProjectionFromFiles()
Create a relational projection:
import { flexibleProjectionFromFiles } from "./lib/axiom/projection/flexible.ts";
const model = await flexibleProjectionFromFiles(["./doc.md"]);
console.log("Documents:", model.documents);
console.log("Nodes:", model.nodes);
console.log("Hierarchies:", model.hierarchies);playbooksFromFiles()
Create an executable playbook projection:
import { playbooksFromFiles } from "./lib/axiom/projection/playbook.ts";
const { tasks, directives, issues, sources } = await playbooksFromFiles([
"./Spryfile.md"
]);
console.log("Tasks:", tasks.map(t => t.taskId()));
console.log("Issues:", issues);Return Type
interface PlaybookProjection {
sources: MarkdownEncountered[];
executablesById: Record<string, Executable>;
executables: Executable[];
materializablesById: Record<string, Materializable>;
materializables: Materializable[];
directivesById: Record<string, Directive>;
directives: Directive[];
tasks: ExecutableTask[];
issues: NodeIssue[];
}Task Execution
Execute tasks in dependency order.
executionPlan()
Build an execution plan:
import { executionPlan } from "./lib/universal/task.ts";
const plan = executionPlan(tasks);
console.log("Task IDs:", plan.ids);
console.log("Layers:", plan.layers);
console.log("DAG order:", plan.dag.map(t => t.taskId()));
console.log("Missing deps:", plan.missingDeps);
console.log("Unresolved:", plan.unresolved);Plan Properties
interface TaskExecutionPlan<T extends Task> {
tasks: readonly T[];
ids: readonly string[];
byId: Readonly<Record<string, T>>;
missingDeps: Readonly<Record<string, string[]>>;
adjacency: Readonly<Record<string, string[]>>;
indegree: Readonly<Record<string, number>>;
edges: readonly [string, string][];
layers: readonly string[][];
dag: readonly T[];
unresolved: readonly string[];
}executionSubplan()
Create a subplan for specific tasks:
import { executionSubplan } from "./lib/universal/task.ts";
const fullPlan = executionPlan(tasks);
const subplan = executionSubplan(fullPlan, ["deploy"]);
// subplan includes deploy and all its dependenciesexecuteDAG()
Execute tasks:
import { executeDAG } from "./lib/universal/task.ts";
const summary = await executeDAG(plan, async (task, sectionStack) => {
// Execute the task
const result = await runTask(task);
return {
disposition: result.success ? "continue" : "terminate",
ctx: { runId: "my-run" },
success: result.success,
exitCode: result.exitCode,
startedAt: result.startedAt,
endedAt: result.endedAt,
};
});tasksRunbook()
High-level task runner:
import { tasksRunbook } from "./lib/axiom/orchestrate/task.ts";
const runbook = tasksRunbook({
directives,
shellBus: myShellEventBus,
tasksBus: myTasksEventBus,
});
const results = await runbook.execute(plan);Event Bus
Subscribe to execution events.
Creating an Event Bus
import { eventBus } from "./lib/universal/event-bus.ts";
interface MyEvents {
"task:start": { taskId: string };
"task:done": { taskId: string; success: boolean };
"task:error": { taskId: string; error: Error };
}
const bus = eventBus<MyEvents>();Subscribing to Events
bus.on("task:start", (event) => {
console.log(`Starting: ${event.taskId}`);
});
bus.on("task:done", (event) => {
console.log(`Done: ${event.taskId}, success: ${event.success}`);
});
bus.on("task:error", (event) => {
console.error(`Error in ${event.taskId}:`, event.error);
});Task Event Types
interface TaskExecEventMap<T, Ctx> {
"plan:start": { plan: TaskExecutionPlan<T> };
"plan:done": { plan: TaskExecutionPlan<T>; summary: ExecutionSummary };
"task:start": { task: T; plan: TaskExecutionPlan<T> };
"task:done": { task: T; result: TaskResult<Ctx> };
"task:error": { task: T; error: Error };
"task:skip": { task: T; reason: string };
}Complete Example
This example demonstrates how to build a complete Spry runner that can parse, analyze, and execute tasks.
import { markdownASTs } from "./lib/axiom/io/mod.ts";
import { graph } from "./lib/axiom/graph.ts";
import { playbooksFromFiles } from "./lib/axiom/projection/playbook.ts";
import { executionPlan } from "./lib/universal/task.ts";
import { tasksRunbook } from "./lib/axiom/orchestrate/task.ts";
import { eventBus } from "./lib/universal/event-bus.ts";
export class SpryRunner {
constructor(private paths: string[]) {}
async parse() {
const results = [];
for await (const md of markdownASTs(this.paths)) {
results.push({
path: md.file.path,
root: md.mdastRoot,
});
}
return results;
}
async analyze() {
const results = [];
for await (const md of markdownASTs(this.paths)) {
results.push({
path: md.file.path,
graph: graph(md.mdastRoot),
});
}
return results;
}
async listTasks() {
const { tasks } = await playbooksFromFiles(this.paths);
return tasks.map(t => ({
id: t.taskId(),
deps: t.taskDeps(),
description: t.spawnableArgs.description,
}));
}
async execute(options?: { verbose?: boolean }) {
const { tasks, directives, issues } = await playbooksFromFiles(this.paths);
if (issues.length > 0 && options?.verbose) {
console.warn("Issues:", issues);
}
const plan = executionPlan(tasks);
const runbook = tasksRunbook({ directives });
return await runbook.execute(plan);
}
async executeTask(taskId: string) {
const { tasks, directives } = await playbooksFromFiles(this.paths);
const plan = executionPlan(tasks);
const { executionSubplan } = await import("./lib/universal/task.ts");
const subplan = executionSubplan(plan, [taskId]);
const runbook = tasksRunbook({ directives });
return await runbook.execute(subplan);
}
}
// Usage
const runner = new SpryRunner(["./Spryfile.md"]);
const tasks = await runner.listTasks();
console.log("Tasks:", tasks);
const results = await runner.execute({ verbose: true });
console.log("Results:", results);TypeScript Types
Core Types
// Task interface
interface Task<Baggage = {}> {
taskId: () => string;
taskDeps?: () => string[] | undefined;
}
// Graph edge
interface GraphEdge<Relationship extends string> {
rel: Relationship;
from: Node;
to: Node;
}
// Executable task
interface ExecutableTask extends Executable {
taskId: () => string;
taskDeps: () => string[];
}Import Locations
// Core parsing
import { markdownASTs, mardownParserPipeline } from "./lib/axiom/io/mod.ts";
// Graph building
import { graph, graphToDot } from "./lib/axiom/graph.ts";
// Projections
import { flexibleProjectionFromFiles } from "./lib/axiom/projection/flexible.ts";
import { playbooksFromFiles } from "./lib/axiom/projection/playbook.ts";
// Task execution
import { executionPlan, executeDAG, executionSubplan } from "./lib/universal/task.ts";
import { tasksRunbook } from "./lib/axiom/orchestrate/task.ts";
// Event handling
import { eventBus } from "./lib/universal/event-bus.ts";
// Utilities
import { shell } from "./lib/universal/shell.ts";
import { dataBag } from "./lib/axiom/mdast/data-bag.ts";How is this guide?
Last updated on