Skip to content
Open
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
40 changes: 40 additions & 0 deletions packages/breadboard/src/board.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { GraphDescriptor, Edge, NodeDescriptor } from "./types.js";

export class Board {
private edges: Edge[] = [];
private nodes: NodeDescriptor[] = [];

serialize(): GraphDescriptor {
return {
edges: this.edges.map((edge) => {
const serialized: Edge = {
from: edge.from,
to: edge.to,
};
if (edge.out !== undefined) serialized.out = edge.out;
if (edge.in !== undefined) serialized.in = edge.in;
if (edge.required !== undefined) serialized.required = edge.required;
return serialized;
}),
nodes: this.nodes,
};
}

static deserialize(descriptor: GraphDescriptor): Board {
const board = new Board();
board.nodes = descriptor.nodes || [];

for (const edge of descriptor.edges || []) {
const newEdge: Edge = {
from: edge.from,
to: edge.to,
};
if (edge.out !== undefined) newEdge.out = edge.out;
if (edge.in !== undefined) newEdge.in = edge.in;
if (edge.required !== undefined) newEdge.required = edge.required;
board.edges.push(newEdge);
}

return board;
}
}
35 changes: 35 additions & 0 deletions packages/breadboard/src/node.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
export interface WireOptions {
required?: boolean;
}

export class Wire {
source: Node;
target: Node;
required: boolean;

constructor(source: Node, target: Node, required: boolean = false) {
this.source = source;
this.target = target;
this.required = required;
}
}

export class Node {
id: string;
outgoing: Wire[] = [];

constructor(id: string) {
this.id = id;
this.outgoing = [];
}

to(target: Node, options?: WireOptions): Wire {
const wire = new Wire(this, target, options?.required);
this.outgoing.push(wire);
return wire;
}

requiredTo(target: Node): Wire {
return this.to(target, { required: true });
}
}
74 changes: 74 additions & 0 deletions packages/breadboard/src/runtime/traversal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

import type { Edge, NodeIdentifier, InputValues } from "../types.js";

interface TraversalEdge {
from: NodeIdentifier;
to: NodeIdentifier;
out: string;
in: string;
required?: boolean;
}

interface NodeState {
inputs: InputValues;
receivedEdges: Set<string>;
}

export class Traversal {
private incomingEdges: Map<NodeIdentifier, TraversalEdge[]> = new Map();
private nodeStates: Map<NodeIdentifier, NodeState> = new Map();

constructor(edges: Edge[]) {
for (const edge of edges) {
const edgesForNode = this.incomingEdges.get(edge.to) || [];
edgesForNode.push(edge as TraversalEdge);
this.incomingEdges.set(edge.to, edgesForNode);
}
}

/**
* Check if a node is ready to execute.
* A node is ready only when all required incoming edges have provided data.
*/
isNodeReady(nodeId: NodeIdentifier): boolean {
const edges = this.incomingEdges.get(nodeId) || [];
const state = this.nodeStates.get(nodeId);

for (const edge of edges) {
if (edge.required) {
const edgeKey = `${edge.from}:${edge.out}`;
if (!state?.receivedEdges.has(edgeKey)) {
return false;
}
}
}

return true;
}

/**
* Receive data from an edge.
*/
receiveEdgeData(nodeId: NodeIdentifier, from: NodeIdentifier, out: string, data: unknown): void {
let state = this.nodeStates.get(nodeId);
if (!state) {
state = { inputs: {}, receivedEdges: new Set() };
this.nodeStates.set(nodeId, state);
}

state.receivedEdges.add(`${from}:${out}`);

if (data !== undefined && typeof data === "object") {
Object.assign(state.inputs, data);
}
}

getNodeInputs(nodeId: NodeIdentifier): InputValues {
return this.nodeStates.get(nodeId)?.inputs || {};
}
}
26 changes: 26 additions & 0 deletions packages/breadboard/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* @license
* Copyright 2024 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/

export type NodeIdentifier = string;

/**
* Represents an edge (wire) connecting two nodes in the graph.
*/
export interface Edge {
from: NodeIdentifier;
to: NodeIdentifier;
out?: string;
in?: string;
required?: boolean;
}

/**
* Proxy interface for building node connections in a fluent API style.
*/
export interface NodeProxy {
to(target: NodeIdentifier): void;
requiredTo(target: NodeIdentifier): void;
}