@breadboard-ai/build
Advanced tools
Comparing version
# Changelog | ||
## 0.9.0 | ||
### Minor Changes | ||
- cc5f4b6: Changes to the signature of the board function. Introduces new inputNode and outputNode functions which are used for polymorphic boards. | ||
- a940b87: The kit function now takes components as an object instead of an array. | ||
- 7de241c: Remove `BoardRunner`. | ||
### Patch Changes | ||
- 374ea85: Internal refactoring (branding) | ||
- f93ec06: Reflect when an input is optional by making its type parameter possibly undefined. | ||
- 398bf4f: Behaviors added with the annotate function are now additive instead of clobbery. | ||
- Updated dependencies [49b3612] | ||
- Updated dependencies [e0dccfe] | ||
- Updated dependencies [6404cb3] | ||
- Updated dependencies [9ad0524] | ||
- Updated dependencies [a4301e6] | ||
- Updated dependencies [7fdd660] | ||
- Updated dependencies [b201e07] | ||
- Updated dependencies [15b5659] | ||
- Updated dependencies [0296c89] | ||
- Updated dependencies [a34bb69] | ||
- Updated dependencies [534d67e] | ||
- Updated dependencies [c397d53] | ||
- Updated dependencies [7de241c] | ||
- Updated dependencies [a424c92] | ||
- Updated dependencies [c2cd40d] | ||
- Updated dependencies [262cefd] | ||
- Updated dependencies [79d709c] | ||
- @google-labs/breadboard@0.25.0 | ||
## 0.8.1 | ||
@@ -4,0 +36,0 @@ |
@@ -6,3 +6,3 @@ /** | ||
*/ | ||
export { board } from "./internal/board/board.js"; | ||
export { board, inputNode, outputNode } from "./internal/board/board.js"; | ||
export { constant } from "./internal/board/constant.js"; | ||
@@ -9,0 +9,0 @@ export { converge } from "./internal/board/converge.js"; |
@@ -6,3 +6,3 @@ /** | ||
*/ | ||
export { board } from "./internal/board/board.js"; | ||
export { board, inputNode, outputNode } from "./internal/board/board.js"; | ||
export { constant } from "./internal/board/constant.js"; | ||
@@ -9,0 +9,0 @@ export { converge } from "./internal/board/converge.js"; |
@@ -7,10 +7,13 @@ /** | ||
import type { NodeDescriberResult } from "@google-labs/breadboard"; | ||
import type { GraphMetadata } from "@google-labs/breadboard-schema/graph.js"; | ||
import type { GraphMetadata, NodeMetadata } from "@google-labs/breadboard-schema/graph.js"; | ||
import type { JSONSchema4 } from "json-schema"; | ||
import type { Value } from "../../index.js"; | ||
import { InputPort, OutputPort, type OutputPortReference, type ValuesOrOutputPorts } from "../common/port.js"; | ||
import type { SerializableInputPort, SerializableOutputPort, SerializableOutputPortReference } from "../common/serializable.js"; | ||
import type { SerializableBoard, SerializableInputPort, SerializableOutputPort, SerializableOutputPortReference } from "../common/serializable.js"; | ||
import { type JsonSerializable } from "../type-system/type.js"; | ||
import { type GenericSpecialInput, type Input, type InputWithDefault } from "./input.js"; | ||
import { type Output } from "./output.js"; | ||
import type { Loopback } from "./loopback.js"; | ||
import type { Convergence } from "./converge.js"; | ||
import type { AutoOptional, Expand, FlattenUnion, RemoveReadonly } from "../common/type-util.js"; | ||
/** | ||
@@ -38,9 +41,6 @@ * Define a new Breadboard board. | ||
*/ | ||
export declare function board<IPORTS extends BoardInputShape, OPORTS extends BoardOutputShape>({ inputs, outputs, id, title, description, version, metadata, }: BoardParameters<IPORTS, OPORTS>): BoardDefinition<FlattenMultiInputs<IPORTS>, FlattenMultiOutputs<OPORTS>>; | ||
export declare function board<const T extends BoardInit>({ inputs, outputs, id, title, description, version, metadata, }: T): BoardDefinition<Expand<AutoOptional<RemoveReadonly<SimplifyBoardInitInputs<T["inputs"]>>>>, Expand<AutoOptional<RemoveReadonly<SimplifyBoardInitOutputs<T["outputs"]>>>>>; | ||
export type FlattenMultiInputs<I extends BoardInputShape> = I extends Array<BoardInputPortsWithUndefined> ? { | ||
[K in keyof I[number] as K extends "$id" | "$metadata" ? never : K]-?: I[number][K] extends Input<infer T> | undefined ? undefined extends I[number][K] ? Input<T | undefined> : Input<T> : never; | ||
[K in keyof I[number] as K extends "$id" | "$metadata" ? never : K]-?: I[number][K] extends Input<infer T> | InputWithDefault<infer T> | undefined ? undefined extends I[number][K] ? Input<T | undefined> : Input<T> : never; | ||
} : I; | ||
type FlattenMultiOutputs<O extends BoardOutputShape> = O extends Array<BoardOutputPortsWithUndefined> ? { | ||
[K in keyof O[number] as K extends "$id" | "$metadata" ? never : K]-?: O[number][K] extends OutputPort<infer T> | undefined ? undefined extends O[number][K] ? OutputPort<T | undefined> : OutputPort<T> : O[number][K] extends OutputPortReference<infer T> | undefined ? undefined extends O[number][K] ? OutputPortReference<T | undefined> : OutputPortReference<T> : never; | ||
} : O; | ||
export interface BoardParameters<IPORTS extends BoardInputShape, OPORTS extends BoardOutputShape> { | ||
@@ -81,3 +81,3 @@ inputs: IPORTS; | ||
}; | ||
export type BoardDefinition<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> = BoardInstantiateFunction<IPORTS, OPORTS> & { | ||
export type OldBoardDefinition<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> = OldBoardInstantiateFunction<IPORTS, OPORTS> & { | ||
readonly id?: string; | ||
@@ -94,5 +94,5 @@ readonly inputs: IPORTS; | ||
}; | ||
export type GenericBoardDefinition = BoardDefinition<any, any>; | ||
type BoardInstantiateFunction<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> = (values: ValuesOrOutputPorts<ExtractPortTypes<IPORTS>>) => BoardInstance<IPORTS, OPORTS>; | ||
export declare class BoardInstance<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> { | ||
export type GenericBoardDefinition = OldBoardDefinition<any, any>; | ||
type OldBoardInstantiateFunction<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> = (values: ValuesOrOutputPorts<ExtractPortTypes<IPORTS>>) => OldBoardInstance<IPORTS, OPORTS>; | ||
export declare class OldBoardInstance<IPORTS extends BoardInputPorts, OPORTS extends BoardOutputPorts> { | ||
#private; | ||
@@ -102,8 +102,8 @@ readonly inputs: IPORTS; | ||
readonly values: ValuesOrOutputPorts<ExtractPortTypes<IPORTS>>; | ||
readonly definition: BoardDefinition<IPORTS, OPORTS>; | ||
constructor(inputs: IPORTS, outputs: OPORTS, values: ValuesOrOutputPorts<ExtractPortTypes<IPORTS>>, definition: BoardDefinition<IPORTS, OPORTS>); | ||
readonly definition: OldBoardDefinition<IPORTS, OPORTS>; | ||
constructor(inputs: IPORTS, outputs: OPORTS, values: ValuesOrOutputPorts<ExtractPortTypes<IPORTS>>, definition: OldBoardDefinition<IPORTS, OPORTS>); | ||
} | ||
export declare function isBoardInstance(value: unknown): value is BoardInstance<BoardInputPorts, BoardOutputPorts>; | ||
export declare function isBoardInstance(value: unknown): value is OldBoardInstance<BoardInputPorts, BoardOutputPorts>; | ||
export type BoardOutput = (OutputPortReference<JsonSerializable> | Output<JsonSerializable> | Input<JsonSerializable> | InputWithDefault<JsonSerializable>) & { | ||
innerBoard: BoardInstance<BoardInputPorts, BoardOutputPorts>; | ||
innerBoard: OldBoardInstance<BoardInputPorts, BoardOutputPorts>; | ||
innerPortName: string; | ||
@@ -126,2 +126,66 @@ }; | ||
export declare function unroll(value: Input<JsonSerializable> | InputWithDefault<JsonSerializable> | Output<JsonSerializable> | OutputPort<JsonSerializable> | OutputPortReference<JsonSerializable> | SerializableOutputPort): Input<JsonSerializable> | InputWithDefault<JsonSerializable> | OutputPort<JsonSerializable> | SerializableOutputPort; | ||
export interface BoardInit { | ||
inputs: InputNode | InputNode[] | AnonymousInputNodeShorthand; | ||
outputs: OutputNode | OutputNode[] | AnonymousOutputNodeShorthand; | ||
id?: string; | ||
title?: string; | ||
description?: string; | ||
version?: string; | ||
metadata?: GraphMetadata; | ||
} | ||
type AnonymousInputNodeShorthand = Record<string, GenericSpecialInput>; | ||
type AnonymousOutputNodeShorthand = Record<string, Value | Output | undefined>; | ||
export type BoardDefinition<I extends Record<string, JsonSerializable | undefined> = any, O extends Record<string, JsonSerializable | undefined> = any> = BoardInstantiateFunction<I, O> & SerializableBoard & { | ||
describe: () => Promise<NodeDescriberResult>; | ||
}; | ||
export type BoardInstantiateFunction<I extends Record<string, JsonSerializable | undefined>, O extends Record<string, JsonSerializable | undefined>> = (inputs: { | ||
[K in keyof I]: Value<I[K]>; | ||
}) => BoardInstance<I, O>; | ||
export interface BoardInstance<I extends Record<string, JsonSerializable | undefined>, O extends Record<string, JsonSerializable | undefined>> { | ||
outputs: Expand<WrapInValues<FilterSerializable<FlattenUnion<O>>>>; | ||
} | ||
type FilterSerializable<T extends Record<string, unknown>> = { | ||
[K in keyof T]: T[K] extends JsonSerializable | undefined ? T[K] : never; | ||
}; | ||
type WrapInValues<T extends Record<string, JsonSerializable | undefined>> = { | ||
[K in keyof T]: Value<T[K]>; | ||
}; | ||
/** | ||
* Board inputs can either be an object (one input node) or an array of objects | ||
* (multiple input nodes). This function returns a union of types in the array | ||
* case. | ||
*/ | ||
type SimplifyBoardInitInputs<T extends BoardInit["inputs"]> = T extends InputNode<infer X> ? X : T extends InputNode[] ? SimplifyBoardInitInputs<T[number]> : T extends AnonymousInputNodeShorthand ? ExtractInputTypes<T> : never; | ||
/** | ||
* | ||
*/ | ||
export type SimplifyBoardInitOutputs<T extends BoardInit["outputs"]> = T extends OutputNode<infer X> ? X : T extends Array<OutputNode> ? SimplifyBoardInitOutputs<T[number]> : T extends AnonymousOutputNodeShorthand ? ExtractOutputTypes<T> : never; | ||
/** | ||
* Pulls out the type parameter for each `Input`, taking care to add `undefined` | ||
* in the case of `InputWithDefault`. This is because when there is a default, | ||
* then it is optional from the caller's perspective. | ||
*/ | ||
type ExtractInputTypes<T extends Record<string, GenericSpecialInput>> = { | ||
[K in keyof T]: T[K] extends Input<infer X> ? X : T[K] extends InputWithDefault<infer X> ? X | undefined : never; | ||
}; | ||
/** | ||
* Pulls out the type parameter for each `Input`, taking care to add `undefined` | ||
* in the case of `InputWithDefault`. This is because when there is a default, | ||
* then it is optional from the caller's perspective. | ||
*/ | ||
type ExtractOutputTypes<T extends Record<string, Value | Output | undefined>> = { | ||
[K in keyof T]: T[K] extends Input<infer X> ? X : T[K] extends InputWithDefault<infer X> ? X | undefined : T[K] extends Output<infer X> ? X : T[K] extends OutputPort<infer X> ? X : T[K] extends OutputPortReference<infer X> ? X : T[K] extends Loopback<infer X> ? X : T[K] extends Convergence<infer X> ? X : T[K] extends Value<infer X> ? X : never; | ||
}; | ||
export declare function inputNode<T extends Record<string, GenericSpecialInput>>(inputs: T, metadata?: NodeMetadata & { | ||
id?: string; | ||
}): InputNode<ExtractInputTypes<T>>; | ||
export interface InputNode<T extends Record<string, JsonSerializable | undefined> = Record<string, JsonSerializable | undefined>> { | ||
isInputNode: true; | ||
} | ||
export declare function outputNode<T extends Record<string, Value | Output | undefined>>(outputs: T, metadata?: NodeMetadata & { | ||
id?: string; | ||
}): OutputNode<Expand<ExtractOutputTypes<T>>>; | ||
export interface OutputNode<T extends Record<string, JsonSerializable | undefined> = Record<string, JsonSerializable | undefined>> { | ||
isOutputNode: true; | ||
} | ||
export {}; |
@@ -11,4 +11,5 @@ /** | ||
import { isSpecialOutput } from "./output.js"; | ||
/* eslint-disable @typescript-eslint/no-explicit-any */ | ||
/* eslint-disable @typescript-eslint/no-unused-vars */ | ||
// TODO(aomarks) Support primary ports in boards. | ||
// TODO(aomarks) Support adding descriptions to board ports. | ||
/** | ||
@@ -97,3 +98,3 @@ * Define a new Breadboard board. | ||
instantiate(values) { | ||
return new BoardInstance(this.#inputs, this.#outputs, values, this.definition); | ||
return new OldBoardInstance(this.#inputs, this.#outputs, values, this.definition); | ||
} | ||
@@ -131,3 +132,3 @@ async describe() { | ||
} | ||
export class BoardInstance { | ||
export class OldBoardInstance { | ||
inputs; | ||
@@ -242,2 +243,26 @@ outputs; | ||
} | ||
export function inputNode(inputs, metadata) { | ||
const result = { ...inputs }; | ||
if (metadata) { | ||
if (metadata.id) { | ||
result.$id = metadata.id; | ||
metadata = { ...metadata }; | ||
delete metadata["id"]; | ||
} | ||
result.$metadata = metadata; | ||
} | ||
return result; | ||
} | ||
export function outputNode(outputs, metadata) { | ||
const result = { ...outputs }; | ||
if (metadata) { | ||
if (metadata.id) { | ||
result.$id = metadata.id; | ||
metadata = { ...metadata }; | ||
delete metadata["id"]; | ||
} | ||
result.$metadata = metadata; | ||
} | ||
return result; | ||
} | ||
//# sourceMappingURL=board.js.map |
@@ -6,2 +6,3 @@ /** | ||
*/ | ||
import { brand } from "../common/brand.js"; | ||
import { type OutputPortReference } from "../common/port.js"; | ||
@@ -21,2 +22,3 @@ import type { JsonSerializable } from "../type-system/type.js"; | ||
interface Constant { | ||
readonly [brand]: "Constant"; | ||
[ConstantVersionOf]: OutputPortReference<JsonSerializable> | Input<JsonSerializable> | InputWithDefault<JsonSerializable>; | ||
@@ -23,0 +25,0 @@ } |
@@ -6,2 +6,3 @@ /** | ||
*/ | ||
import { brand, isBranded } from "../common/brand.js"; | ||
import {} from "../common/port.js"; | ||
@@ -23,2 +24,3 @@ // TODO(aomarks) Possible other names: `sticky`, `persistent`, `retained`. | ||
return { | ||
[brand]: "Constant", | ||
...output, | ||
@@ -30,6 +32,4 @@ [ConstantVersionOf]: output, | ||
export function isConstant(value) { | ||
return (typeof value === "object" && | ||
value !== null && | ||
value[ConstantVersionOf] !== undefined); | ||
return isBranded(value, "Constant"); | ||
} | ||
//# sourceMappingURL=constant.js.map |
@@ -6,2 +6,3 @@ /** | ||
*/ | ||
import { brand } from "../common/brand.js"; | ||
import type { OutputPortReference } from "../common/port.js"; | ||
@@ -15,7 +16,7 @@ import type { BroadenBasicType } from "../common/type-util.js"; | ||
type ExtractType<T extends Convergable<JsonSerializable>> = T extends Convergable<infer X> ? X extends string | number | boolean ? BroadenBasicType<X> : X : never; | ||
export interface Convergence<T extends JsonSerializable> { | ||
__isConvergence: true; | ||
export interface Convergence<T extends JsonSerializable = JsonSerializable> { | ||
readonly [brand]: "Convergence"; | ||
ports: Array<Convergable<T>>; | ||
} | ||
export declare function isConvergence(value: unknown): value is Convergence<JsonSerializable>; | ||
export declare function isConvergence(value: unknown): value is Convergence; | ||
export {}; |
@@ -6,5 +6,6 @@ /** | ||
*/ | ||
import { brand, isBranded } from "../common/brand.js"; | ||
export function converge(first, second, ...rest) { | ||
return { | ||
__isConvergence: true, | ||
[brand]: "Convergence", | ||
ports: [first, second, ...rest], | ||
@@ -14,6 +15,4 @@ }; | ||
export function isConvergence(value) { | ||
return (typeof value === "object" && | ||
value !== null && | ||
value.__isConvergence === true); | ||
return isBranded(value, "Convergence"); | ||
} | ||
//# sourceMappingURL=converge.js.map |
@@ -8,2 +8,3 @@ /** | ||
import type { BreadboardType, ConvertBreadboardType, JsonSerializable } from "../type-system/type.js"; | ||
type Optionalize<T extends Record<string, unknown>, X extends JsonSerializable | undefined> = T["optional"] extends true ? X | undefined : X; | ||
export declare function input(): Input<string>; | ||
@@ -16,3 +17,3 @@ export declare function input<T extends Record<string, unknown>>(params: T & { | ||
type: Defined; | ||
} & CheckParams<T>): Input<T["type"] extends BreadboardType ? ConvertBreadboardType<T["type"]> : JsonSerializable>; | ||
} & CheckParams<T>): Input<Optionalize<T, T["type"] extends BreadboardType ? ConvertBreadboardType<T["type"]> : JsonSerializable>>; | ||
export declare function input<T extends Record<string, unknown>>(params: T & LooseParams & { | ||
@@ -29,6 +30,16 @@ default: Defined; | ||
examples?: undefined; | ||
optional?: undefined; | ||
}): Input<string>; | ||
export declare function input(params: { | ||
$id?: string; | ||
description?: string; | ||
title?: string; | ||
type?: undefined; | ||
default?: undefined; | ||
examples?: undefined; | ||
optional: true; | ||
}): Input<string | undefined>; | ||
export declare function input<T extends Record<string, unknown>>(params: T & { | ||
examples: Defined; | ||
} & CheckParams<T>): Input<T["examples"] extends string[] | number[] | boolean[] ? BroadenBasicType<T["examples"][number]> : JsonSerializable>; | ||
} & CheckParams<T>): Input<Optionalize<T, T["examples"] extends string[] | number[] | boolean[] ? BroadenBasicType<T["examples"][number]> : JsonSerializable>>; | ||
interface LooseParams { | ||
@@ -63,3 +74,3 @@ $id?: string; | ||
} | ||
export type GenericSpecialInput = Input<JsonSerializable> | InputWithDefault<JsonSerializable>; | ||
export type GenericSpecialInput = Input<JsonSerializable | undefined> | InputWithDefault<JsonSerializable>; | ||
type CheckParams<T extends LooseParams> = (T["type"] extends Defined ? { | ||
@@ -66,0 +77,0 @@ $id?: string; |
@@ -6,2 +6,3 @@ /** | ||
*/ | ||
import { brand } from "../common/brand.js"; | ||
import type { OutputPortReference } from "../common/port.js"; | ||
@@ -23,5 +24,5 @@ import type { BreadboardType, ConvertBreadboardType, JsonSerializable } from "../type-system/type.js"; | ||
*/ | ||
declare class Loopback<T extends JsonSerializable> { | ||
declare class Loopback<T extends JsonSerializable = JsonSerializable> { | ||
#private; | ||
readonly __BreadboardEntity__ = "Loopback"; | ||
readonly [brand] = "Loopback"; | ||
readonly type: BreadboardType; | ||
@@ -42,2 +43,2 @@ constructor(type: BreadboardType); | ||
*/ | ||
export declare function isLoopback(value: unknown): value is Loopback<JsonSerializable>; | ||
export declare function isLoopback(value: unknown): value is Loopback; |
@@ -6,2 +6,3 @@ /** | ||
*/ | ||
import { brand, isBranded } from "../common/brand.js"; | ||
/** | ||
@@ -20,3 +21,2 @@ * Create a new Breadboard loopback. | ||
} | ||
const LOOPBACK_ENTITY_NAME = "Loopback"; | ||
/** | ||
@@ -31,3 +31,3 @@ * Loopbacks can be used in place of real output ports for situations where it | ||
class Loopback { | ||
__BreadboardEntity__ = LOOPBACK_ENTITY_NAME; | ||
[brand] = "Loopback"; | ||
type; | ||
@@ -59,10 +59,4 @@ #value; | ||
export function isLoopback(value) { | ||
return isBreadboardEntity(value, LOOPBACK_ENTITY_NAME); | ||
return isBranded(value, "Loopback"); | ||
} | ||
// TODO(aomarks) Use this pattern elsewhere. | ||
function isBreadboardEntity(value, type) { | ||
return (typeof value === "object" && | ||
value !== null && | ||
value.__BreadboardEntity__ === type); | ||
} | ||
//# sourceMappingURL=loopback.js.map |
@@ -6,2 +6,3 @@ /** | ||
*/ | ||
import { brand, isBranded } from "../common/brand.js"; | ||
import {} from "../common/port.js"; | ||
@@ -18,2 +19,3 @@ /** | ||
return { | ||
[brand]: "Optional", | ||
...output, | ||
@@ -25,6 +27,4 @@ [OptionalVersionOf]: output, | ||
export function isOptional(value) { | ||
return (typeof value === "object" && | ||
value !== null && | ||
value[OptionalVersionOf] !== undefined); | ||
return isBranded(value, "Optional"); | ||
} | ||
//# sourceMappingURL=optional.js.map |
@@ -6,6 +6,6 @@ /** | ||
*/ | ||
import type { OutputPortReference } from "../common/port.js"; | ||
import { brand } from "../common/brand.js"; | ||
import type { Value } from "../common/value.js"; | ||
import type { JsonSerializable } from "../type-system/type.js"; | ||
import type { Input, InputWithDefault } from "./input.js"; | ||
export declare function output<T extends JsonSerializable>(port: Output<T> | OutputPortReference<T> | Input<T> | InputWithDefault<T>, { id, title, description, }?: { | ||
export declare function output<T extends JsonSerializable | undefined>(port: Value<T>, { id, title, description, }?: { | ||
id?: string; | ||
@@ -15,9 +15,9 @@ title?: string; | ||
}): Output<T>; | ||
export interface Output<T extends JsonSerializable | undefined> { | ||
readonly __SpecialOutputBrand: true; | ||
export interface Output<T extends JsonSerializable | undefined = JsonSerializable | undefined> { | ||
readonly [brand]: "Output"; | ||
readonly id?: string; | ||
readonly title?: string; | ||
readonly description?: string; | ||
readonly port: Output<T> | OutputPortReference<T> | Input<T> | InputWithDefault<T>; | ||
readonly port: Value<T>; | ||
} | ||
export declare function isSpecialOutput(value: unknown): value is Output<JsonSerializable>; | ||
export declare function isSpecialOutput(value: unknown): value is Output; |
@@ -6,5 +6,6 @@ /** | ||
*/ | ||
import { brand, isBranded } from "../common/brand.js"; | ||
export function output(port, { id, title, description, } = {}) { | ||
return { | ||
__SpecialOutputBrand: true, | ||
[brand]: "Output", | ||
id, | ||
@@ -17,6 +18,4 @@ title, | ||
export function isSpecialOutput(value) { | ||
return (typeof value === "object" && | ||
value !== null && | ||
"__SpecialOutputBrand" in value); | ||
return isBranded(value, "Output"); | ||
} | ||
//# sourceMappingURL=output.js.map |
@@ -8,6 +8,6 @@ /** | ||
import {} from "../type-system/type.js"; | ||
import { BoardInstance, describeInput, describeOutput, isBoard, isBoardInstance, isBoardOutput, isSerializableOutputPortReference, } from "./board.js"; | ||
import { OldBoardInstance, describeInput, describeOutput, isBoard, isBoardInstance, isBoardOutput, isSerializableOutputPortReference, } from "./board.js"; | ||
import { ConstantVersionOf, isConstant } from "./constant.js"; | ||
import { isConvergence } from "./converge.js"; | ||
import { isSpecialInput } from "./input.js"; | ||
import { isSpecialInput, } from "./input.js"; | ||
import { isLoopback } from "./loopback.js"; | ||
@@ -14,0 +14,0 @@ import { OptionalVersionOf, isOptional } from "./optional.js"; |
@@ -19,2 +19,3 @@ /** | ||
outputsForSerialization: Record<string, SerializableOutputPortReference | Output<JsonSerializable> | Input<JsonSerializable> | InputWithDefault<JsonSerializable>> | Array<Record<string, SerializableOutputPortReference | Output<JsonSerializable> | Input<JsonSerializable> | InputWithDefault<JsonSerializable>>>; | ||
id?: string; | ||
title?: string; | ||
@@ -21,0 +22,0 @@ description?: string; |
@@ -50,2 +50,21 @@ /** | ||
export type IsNever<T> = [T] extends [never] ? true : false; | ||
/** | ||
* Remove all readonly modifiers on an object. | ||
*/ | ||
export type RemoveReadonly<T> = { | ||
-readonly [K in keyof T]: T[K]; | ||
}; | ||
/** | ||
* For each property in an object, if its value can be `undefined`, mark that | ||
* property as optional. | ||
*/ | ||
export type AutoOptional<T> = T extends unknown ? { | ||
[K in keyof T as undefined extends T[K] ? K : never]?: T[K]; | ||
} & { | ||
[K in keyof T as undefined extends T[K] ? never : K]: T[K]; | ||
} : never; | ||
export type FlattenUnion<T> = { | ||
[K in keyof UnionToIntersection<T>]: K extends keyof T ? T[K] : UnionToIntersection<T>[K] | undefined; | ||
}; | ||
export type UnionToIntersection<U> = (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never; | ||
export {}; |
@@ -10,3 +10,3 @@ /** | ||
import { type BreadboardType, type JsonSerializable } from "../type-system/type.js"; | ||
import { type OutputPortReference } from "./port.js"; | ||
import { OutputPort, type OutputPortReference } from "./port.js"; | ||
/** | ||
@@ -23,3 +23,3 @@ * A value, or something that can stand-in for a value when wiring together | ||
*/ | ||
export type Value<T extends JsonSerializable> = T | OutputPortReference<T> | Input<T> | InputWithDefault<T> | Loopback<T> | Convergence<T>; | ||
export type Value<T extends JsonSerializable | undefined = JsonSerializable | undefined> = T | OutputPort<T> | OutputPortReference<T> | Input<T> | InputWithDefault<T> | Loopback<Exclude<T, /* TODO(aomarks) Questionable */ undefined>> | Convergence<Exclude<T, /* TODO(aomarks) Questionable */ undefined>>; | ||
/** | ||
@@ -26,0 +26,0 @@ * Given a Breadboard {@link Value}, determine its JSON Schema type. |
@@ -11,3 +11,3 @@ /** | ||
import {} from "../type-system/type.js"; | ||
import { isOutputPortReference, OutputPortGetter, } from "./port.js"; | ||
import { isOutputPortReference, OutputPort, OutputPortGetter, } from "./port.js"; | ||
/** | ||
@@ -14,0 +14,0 @@ * Given a Breadboard {@link Value}, determine its JSON Schema type. |
@@ -18,3 +18,3 @@ /** | ||
import type { Convergence } from "../board/converge.js"; | ||
import type { BoardDefinition } from "../board/board.js"; | ||
import type { SerializableBoard } from "../common/serializable.js"; | ||
export interface Definition<SI extends { | ||
@@ -32,9 +32,3 @@ [K: string]: JsonSerializable; | ||
} | ||
export type GenericDiscreteComponent = Definition<{ | ||
[K: string]: JsonSerializable; | ||
}, { | ||
[K: string]: JsonSerializable; | ||
}, JsonSerializable | undefined, JsonSerializable | undefined, string, boolean, string | false, string | false, { | ||
[K: string]: InputMetadata; | ||
}>; | ||
export type GenericDiscreteComponent = Definition<any, any, any, any, any, any, any, any, any>; | ||
export declare function isDiscreteComponent(value: object): value is GenericDiscreteComponent; | ||
@@ -73,6 +67,5 @@ export declare class DefinitionImpl<SI extends { | ||
} & { | ||
[K in keyof Omit<SI, OI | "$id" | "$metadata">]: IM[K extends keyof IM ? K : never]["board"] extends true ? // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
InstantiateArg<SI[K]> | BoardDefinition<any, any> : InstantiateArg<SI[K]>; | ||
[K in keyof Omit<SI, OI | "$id" | "$metadata">]: IM[K extends keyof IM ? K : never]["board"] extends true ? InstantiateArg<SI[K]> | SerializableBoard : InstantiateArg<SI[K]>; | ||
} & { | ||
[K in OI]?: InstantiateArg<SI[K]> | OutputPortReference<SI[K] | undefined> | undefined; | ||
[K in OI]?: InstantiateArg<SI[K] | undefined> | OutputPortReference<SI[K] | undefined> | undefined; | ||
} & { | ||
@@ -93,3 +86,3 @@ [K in keyof Omit<A, keyof SI | "$id" | "$metadata">]: DI extends JsonSerializable ? InstantiateArg<DI> : never; | ||
}> : SO; | ||
type InstantiateArg<T extends JsonSerializable> = T | OutputPortReference<T> | Input<T> | InputWithDefault<T> | Loopback<T> | Convergence<T>; | ||
type InstantiateArg<T extends JsonSerializable | undefined> = T | OutputPortReference<T> | Input<T> | InputWithDefault<T> | Loopback<Exclude<T, /* TODO(aomarks) questionable */ undefined>> | Convergence<Exclude<T, /* TODO(aomarks) questionable */ undefined>>; | ||
export {}; |
@@ -14,2 +14,3 @@ /** | ||
import { normalizeBreadboardError } from "../common/error.js"; | ||
/* eslint-enable @typescript-eslint/no-explicit-any */ | ||
export function isDiscreteComponent(value) { | ||
@@ -16,0 +17,0 @@ return ("breadboardType" in value && value.breadboardType === "DiscreteComponent"); |
@@ -7,6 +7,7 @@ /** | ||
import type { Kit, KitConstructor } from "@google-labs/breadboard"; | ||
import type { GenericBoardDefinition } from "./board/board.js"; | ||
import type { BoardDefinition } from "./board/board.js"; | ||
import { type GenericDiscreteComponent } from "./define/definition.js"; | ||
import type { KitTag } from "@google-labs/breadboard-schema/graph.js"; | ||
export interface KitOptions { | ||
type ComponentManifest = Record<string, GenericDiscreteComponent | BoardDefinition>; | ||
export interface KitOptions<T extends ComponentManifest> { | ||
title: string; | ||
@@ -17,4 +18,5 @@ description: string; | ||
tags?: KitTag[]; | ||
components: Array<GenericDiscreteComponent | GenericBoardDefinition>; | ||
components: T; | ||
} | ||
export declare function kit(options: KitOptions): KitConstructor<Kit>; | ||
export declare function kit<T extends ComponentManifest>(options: KitOptions<T>): KitConstructor<Kit> & T; | ||
export {}; |
@@ -9,3 +9,7 @@ /** | ||
export function kit(options) { | ||
const handlers = Object.fromEntries(options.components.map((component) => { | ||
const componentsWithIds = Object.fromEntries(Object.entries(options.components).map(([id, component]) => [ | ||
id, | ||
{ ...component, id }, | ||
])); | ||
const handlers = Object.fromEntries(Object.values(componentsWithIds).map((component) => { | ||
if (isDiscreteComponent(component)) { | ||
@@ -22,2 +26,3 @@ return [component.id, component]; | ||
// TODO(aomarks) Should this just be the invoke() method on Board? | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
makeBoardComponentHandler(component), | ||
@@ -29,3 +34,3 @@ ]; | ||
// certain fields on both the static and instance sides. | ||
return class GeneratedBreadboardKit { | ||
const result = class GeneratedBreadboardKit { | ||
static handlers = handlers; | ||
@@ -40,2 +45,3 @@ static url = options.url; | ||
}; | ||
return Object.assign(result, componentsWithIds); | ||
} | ||
@@ -42,0 +48,0 @@ function makeBoardComponentHandler(board) { |
@@ -11,6 +11,10 @@ /** | ||
export function annotate(value, annotations) { | ||
const original = toJSONSchema(value); | ||
const behavior = [ | ||
...new Set([...(original.behavior ?? []), ...annotations.behavior]), | ||
]; | ||
return { | ||
jsonSchema: { | ||
...annotations, | ||
...toJSONSchema(value), | ||
...original, | ||
behavior, | ||
}, | ||
@@ -17,0 +21,0 @@ }; |
{ | ||
"name": "@breadboard-ai/build", | ||
"version": "0.8.1", | ||
"version": "0.9.0", | ||
"description": "JavaScript library for building boards and defining new node types for the Breadboard AI prototyping library", | ||
@@ -118,3 +118,3 @@ "license": "Apache-2.0", | ||
"dependencies": { | ||
"@google-labs/breadboard": "^0.24.0", | ||
"@google-labs/breadboard": "^0.25.0", | ||
"@types/json-schema": "^7.0.15" | ||
@@ -126,4 +126,4 @@ }, | ||
"typescript": "^5.5.4", | ||
"wireit": "^0.14.5" | ||
"wireit": "^0.14.8" | ||
} | ||
} |
420
README.md
@@ -5,5 +5,12 @@ # Breadboard Build | ||
A JavaScript library for defining new node types for the Breadboard AI | ||
prototyping library. | ||
The Breadboard Build API allows you to design and compose boards with | ||
[TypeScript](https://www.typescriptlang.org/). | ||
It is an alternative to the Visual Editor, designed for users who prefer a | ||
code-first approach to working with Breadboard. | ||
Boards that you create with the Build API can be serialized to BGL (Breadboard | ||
Graph Language), which can then be executed directly, or imported into the | ||
Visual Editor. | ||
## Install | ||
@@ -15,397 +22,46 @@ | ||
## Defining new node types | ||
## Documentation | ||
Use the `defineNodeType` function to define a new Breadboard node type. | ||
Please refer to the [Build | ||
API](https://breadboard-ai.github.io/breadboard/docs/build/) section of the | ||
Breadboard Documentation site for full documentation. | ||
### Example | ||
## Example | ||
```ts | ||
import { defineNodeType } from "@breadboard-ai/build"; | ||
import { board, input, output } from "@breadboard-ai/build"; | ||
import { prompt } from "@google-labs/template-kit"; | ||
import { geminiText } from "@google-labs/gemini-kit"; | ||
export const reverseString = defineNodeType({ | ||
name: "example", | ||
inputs: { | ||
forwards: { | ||
type: "string", | ||
description: "The string to reverse", | ||
}, | ||
}, | ||
outputs: { | ||
backwards: { | ||
type: "string", | ||
description: "The reversed string", | ||
primary: true, | ||
}, | ||
}, | ||
invoke: ({ forwards }) => { | ||
return { | ||
backwards: forwards.split("").reverse().join(""), | ||
}; | ||
}, | ||
const topic = input({ | ||
description: "What should the poem be about?", | ||
examples: ["Coffee in the morning", "The mind of a cat"], | ||
}); | ||
``` | ||
### `inputs` and `outputs` | ||
const stanzas = input({ | ||
type: "number", | ||
description: "How many stanzas should the poem have?", | ||
default: 4, | ||
}); | ||
The `inputs` and `outputs` properties specify the input and output ports of the | ||
node, as a map from port name to a port configuration. Port configurations have | ||
the following fields: | ||
const poemPrompt = prompt` | ||
Write a poem about ${topic} with ${stanzas} stanzas.`; | ||
- `type`: (Required) A [Breadboard Type | ||
Expression](#breadboard-type-expressions) (see below). All values sent or | ||
received on this port must conform to this type. | ||
- `description`: (Recommended) A brief description of the port, which will be | ||
displayed in the Breadboard visual editor and in other places where | ||
introspection/debugging is performed. | ||
- `title`: (Optional) A concise title for this input. Defaults to the name of | ||
the port. | ||
- `default`: (Optional) A default value for this input. | ||
- `format`: (Optional) Additional information about the format of the value. | ||
Primarily used to determine how strings are displayed in the Breadboard Visual | ||
Editor. Valid values: | ||
- `multiline`: A string that is likely to contain multiple lines. | ||
- `javascript`: A string that is JavaScript code. | ||
- `primary`: (Optional) Enables a syntactic sugar feature for an output port to | ||
make wiring nodes more concise. When a node has a `primary` output port, then | ||
it becomes possible to use the node itself in API positions where an output | ||
port is expected, with the node's `primary` port being automatically selected | ||
as the default. There can only be one `primary `output port for a node type. | ||
### `invoke` | ||
The `invoke` function specifies the computation that the node will perform at | ||
runtime. It is passed an object which contains values for all of its input | ||
ports, and is expected to return an object with values for all of its output | ||
ports, or a promise thereof if async work is required. | ||
## Polymorphic node types | ||
Usually, a Breadboard node type has a fixed set of input and output ports, as was | ||
the case for the `reverseString` example above. We call these node types | ||
_monomorphic_, because they have one shape. | ||
However, sometimes it is necessary for a node's input and output ports to change | ||
during the course of execution. We call these nodes _polymorphic_, because they | ||
have multiple shapes. | ||
### Polymorphic example | ||
The following is an example of a polymorphic node type. It performs configurable | ||
substitution of values into a string containing placeholders. It has one fixed | ||
input (`template`), multiple _dynamic_ inputs, and one fixed output. | ||
```ts | ||
import { defineNodeType } from "@breadboard-ai/build"; | ||
export const templater = defineNodeType({ | ||
name: "example", | ||
inputs: { | ||
template: { | ||
type: "string", | ||
description: "A template with {{placeholders}}.", | ||
}, | ||
"*": { | ||
type: "string", | ||
description: "Values to fill into template's {{placeholders}}.", | ||
}, | ||
}, | ||
outputs: { | ||
result: { | ||
type: "string", | ||
description: "The template with {{placeholders}} substituted.", | ||
}, | ||
}, | ||
describe: ({ template }) => ({ | ||
inputs: extractPlaceholders(template ?? ""), | ||
}), | ||
invoke: ({ template }, placeholders) => ({ | ||
result: substituteTemplatePlaceholders(template, placeholders), | ||
}), | ||
const poemWriter = geminiText({ | ||
text: poemPrompt, | ||
model: "gemini-1.5-flash-latest", | ||
}); | ||
``` | ||
### Dynamic (`*`) `inputs` and `outputs` | ||
The special `*` port name is used to signify that this node can dynamically | ||
create input ports at runtime, and what types those ports will have. | ||
### polymorphic `invoke` | ||
The `invoke` function for polymorphic nodes is very similar to [invoke for | ||
monomorphic nodes](#invoke), except that an additional second parameter is | ||
passed to the function which contains the input values for the dynamic values. | ||
### `reflective` | ||
Setting `reflective: true` on the `*` output configuration tells Breadboard that | ||
all dynamically created input ports will have a corresponding output port. | ||
### `describe` | ||
The `describe` function allows you to tell Breadboard which input and output | ||
ports are valid and open at runtime based on some particular set of input | ||
values. | ||
A `describe` function will be passed a set of values (in the same way as | ||
`invoke`), and should return an object containing either or both of `inputs` and | ||
`outputs`, which can either be an array of strings or an object. When an array | ||
of strings, the strings are the names of the ports to open. When an object, the | ||
keys are the names of the ports to open, and the values are an object matching | ||
`{description: string}`. | ||
For example, in `templater` above, the `describe` function parses the static | ||
`template` input and opens a port for each of the template's placeholders. | ||
In most situations a `describe` function is not required, because Breadboard can | ||
use the port configuration and instantiation values to determine the ports | ||
automatically. See the following table to check whether you should provide a | ||
`describe` function, and if so whether it should return `inputs`, `outputs`, or | ||
both. (Note that _Dynamic_ means there is a special `*` port configuration, and | ||
_Static_ means there is not.) | ||
| Inputs | Outputs | Describe Inputs | Describe Outputs | | ||
| ------- | ---------- | --------------- | ---------------- | | ||
| Static | Static | N/A | N/A | | ||
| Dynamic | Static | Optional | N/A | | ||
| Static | Dynamic | N/A | **Required** | | ||
| Dynamic | Dynamic | Optional | **Required** | | ||
| Dynamic | Reflective | Optional | N/A | | ||
### `unsafeOutput` | ||
When a node has dynamic outputs, but is not `reflective`, it is not possible at | ||
compile time for Breadboard to know what the valid output ports of a node are. | ||
In this case, use the `unsafeOutput` method to get an output port with a given | ||
name. Note that there is no guarantee this port will exist at runtime, so a | ||
runtime error could occur. | ||
(Note that the following example is highly contrived. It is better to find a | ||
way to use fully static or reflective nodes whenever possible to avoid the use | ||
of `unsafeOutput`). | ||
```ts | ||
import { defineNodeType, array } from "@breadboard-ai/build"; | ||
const weirdStringLength = defineNodeType({ | ||
name: "weirdStringLength", | ||
inputs: { | ||
strings: { type: array("string") }, | ||
}, | ||
outputs: { | ||
"*": { type: "number" }, | ||
}, | ||
describe: ({ strings }) => ({ | ||
outputs: strings, | ||
}), | ||
invoke: ({ strings }) => | ||
Object.fromEntries(strings.map((name) => [name, name.length])), | ||
const poem = output(poemWriter.outputs.text, { | ||
title: "Poem", | ||
description: "The poem that Gemini generated.", | ||
}); | ||
const lengths = weirdStringLength({ strings: ["foo", "bar"] }); | ||
// All 3 of these variables will have type OutputPort<number> and can be wired | ||
// up to other nodes and boards as normal, but only `foo` and `bar` will | ||
// *actually* be valid at runtime. | ||
const foo = lengths.unsafeOutput("foo"); | ||
const bar = lengths.unsafeOutput("bar"); | ||
const baz = lengths.unsafeOutput("baz"); // Oops! | ||
``` | ||
## Adding nodes to Kits | ||
Nodes created with `@breadboard-ai/build` can be directly integrated into Kits | ||
created with `@google-labs/breadboard`. In addition, the | ||
`NodeFactoryFromDefinition` type utility automatically provides a type that can | ||
be used to generate the kit's signature. | ||
```ts | ||
import { reverseString } from "./reverse-string.js"; | ||
import { templater } from "./templater.js"; | ||
import { KitBuilder } from "@google-labs/breadboard/kits"; | ||
import { addKit } from "@google-labs/breadboard"; | ||
import type { NodeFactoryFromDefinition } from "@breadboard-ai/build"; | ||
const ExampleKit = new KitBuilder({ | ||
title: "Example Kit", | ||
description: "An example kit", | ||
version: "0.1.0", | ||
url: "npm:@breadboard-ai/example-kit", | ||
}).build({ reverseString, templater }); | ||
export default ExampleKit; | ||
export const exampleKit = addKit(ExampleKit) as { | ||
reverseString: NodeFactoryFromDefinition<typeof reverseString>; | ||
templater: NodeFactoryFromDefinition<typeof templater>; | ||
}; | ||
``` | ||
## Building boards | ||
In Breadboard, nodes defined by `defineNodeType`, (and coming soon, other nested | ||
boards) can be composed into an executable program called a _board_. Boards are | ||
created using the `board` function. | ||
### Example | ||
```ts | ||
import { board, input } from "@breadboard-ai/build"; | ||
import { reverseString, prompt } from "../build-example-kit.js"; | ||
const word = input({ description: "The word to reverse" }); | ||
const reversed = reverseString({ forwards: word }); | ||
const result = prompt`The word "${word}" is "${reversed}" in reverse`; | ||
export default board({ | ||
title: "Example of @breadboard-ai/build", | ||
description: "A simple example of using the @breadboard-ai/build API", | ||
version: "1.0.0", | ||
inputs: { word }, | ||
outputs: { result }, | ||
id: "poem-writer", | ||
title: "Poem Writer", | ||
description: "Write a poem with Gemini.", | ||
inputs: { topic, stanzas }, | ||
outputs: { poem }, | ||
}); | ||
``` | ||
### Inputs | ||
The special `input` function is how you declare a value that the user of your | ||
board will provide. You can use an `input` anywhere a value of that type is | ||
accepted. | ||
Inputs are typed as `string` by default. To set a different type, use the `type` | ||
property. See [Breadboard type expressions](#breadboard-type-expressions) for | ||
information about what kinds of types can be configured here. | ||
```ts | ||
import { input, array } from "@breadboard-ai/build"; | ||
const operands = input({ | ||
description: "The numbers to sum", | ||
type: array("number"), | ||
}); | ||
``` | ||
Note that when you use an `input` anywhere in your board, you need to also | ||
provide that `input` to the `board` function. If you use an `input` without | ||
providing it to `board`, an error will be raised during serialization. (This is | ||
required so that TypeScript understands the input/output signature of the board, | ||
which allows it to qprovide compile-time type-safety for when boards are nested | ||
into other boards (coming soon).) | ||
### Outputs | ||
Board outputs are configured by directly passing the output ports that you wish | ||
to expose (or nodes that have a [primary](#inputs-and-outputs) output port) to | ||
the `outputs` property. | ||
### Metadata | ||
The optional `title`, `description`, and `version` fields are currently only | ||
used by systems such as the Breadboard Visual Editor, for the purposes of | ||
finding and indexing boards. | ||
### Cycles & Loopbacks | ||
Occasionally it is desirable to create a board with _cycles_. However, | ||
instantiating a node normally requires immediately providing a value for all | ||
inputs. This is a problem because when building a cycle, there will always be an | ||
input which needs to be connected to an output which has not yet been | ||
initialized, and so cannot be referenced. | ||
For such situations involving cycles, the `loopback` function is used to create | ||
an object whose value will be provided at some later time, namely with the | ||
missing link in the cycle. | ||
<!-- TODO(aomarks) Provide a more realistic example here. --> | ||
```ts | ||
import { loopback } from "@breadboard-ai/build"; | ||
const bPlaceholder = loopback({ type: "number" }); | ||
const a = someNode({ value: bPlaceholder }); | ||
const b = someNode({ value: a.outputs.result }); | ||
bPlaceholder.resolve(b.outputs.result); | ||
``` | ||
### Serialization | ||
Use the `serialize` function to convert a `board` result to BGL (Breadboard | ||
Graph Language, a portable JSON format), which allows it to be executed by the | ||
Breadboard runner: | ||
```ts | ||
import { board, serialize } from "@breadboard-ai/build"; | ||
const myBoard = board(...); | ||
const bgl = serialize(myBoard); | ||
console.log(JSON.stringify(bgl, null, 2)); | ||
``` | ||
## Breadboard type expressions | ||
Breadboard type expressions are effectively a common subset of JSON schema and | ||
the TypeScript type system. By using a Breadboard type expression when declaring | ||
your ports, those types will be natively understood by both the Breadboard | ||
runtime (which uses JSON Schema), and when using this node type in the | ||
TypeScript API. | ||
### Basic types | ||
- `"string"` | ||
- `"number"` | ||
- `"boolean"` | ||
- `"null"` | ||
- `"unknown"` | ||
### Utility types | ||
- `array(<type>)`: A function which generates a JSON Schema `array` and its | ||
corresponding TypeScript `Array<...>` type. | ||
- `object({ prop1: <type1>, prop2: <type2>, ... }, [<additional>])`: A function | ||
which generates a JSON Schema `object` and its corresponding TypeScript | ||
`{...}` type. If the optional second argument is set, then the object will | ||
also allow additional properties of the given type. | ||
- `anyOf(<type1>, <type2>, ...)`: A function which generates a JSON Schema | ||
`anyOf` and its corresponding TypeScript union (`type1 | type2`). | ||
- `enumeration(<type1>, <type2>, ...)`: A function which generates a JSON Schema | ||
`enum` and its corresponding TypeScript union (`type1 | type2`). | ||
### Unsafe type escape hatch | ||
The `unsafeType` function can be used as a last resort escape hatch when the | ||
above provided types are not sufficient. It allows you to directly specify JSON | ||
schema and a TypeScript type: | ||
```ts | ||
import { unsafeType } from "@breadboard-ai/build"; | ||
const myCrazyType = unsafeType<{ foo: string }>({ | ||
type: "object", | ||
properties: { | ||
foo: { | ||
type: "string", | ||
}, | ||
}, | ||
required: ["foo"], | ||
}); | ||
``` | ||
## Known issues | ||
1. The `context` object is not yet passed to `invoke`, so certain low-level | ||
operations are not yet possible. | ||
2. There is no way to specify a description for a board's output (probably an | ||
`output` function, similar to `input`, is the solution there). | ||
3. You cannot yet embed boards into other boards (this will work by | ||
instantiating a board object just like a regular node, but during | ||
serialization an `invoke` node will be created in its place.) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
366633
1.54%108
2.86%3641
4.78%66
-83.9%+ Added
- Removed