### Build the library at the repository root Source: https://github.com/yahoo/bgjs/blob/master/examples/README.md Run this command at the repository root to build the library before using any example. Each example package depends on the library via "behavior-graph": "file:../..". ```bash npm install && npm run build ``` -------------------------------- ### Install behavior-graph via npm Source: https://github.com/yahoo/bgjs/blob/master/README.md Install the behavior-graph package from NPM. This is the standard setup step before importing the library in a project. ```bash npm install behavior-graph ``` -------------------------------- ### Minimal behavior-graph extent: press counter with timer and greeting Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Minimal skeleton for a press counter with an auto-reset timer and greeting lookup. The behavior-graph example shows inputs, state, behaviors with demands/supplies, side effects, disposal with ExtentRemoveStrategy.containedLifetimes, and graph wiring. Test by injecting a fake fetchGreeting and using fake timers; all graph work is synchronous except the promise. ```typescript import { Graph, Extent, ExtentRemoveStrategy } from "behavior-graph"; type View = { count: number; greeting: string | null; loading: boolean }; // The outside world, injected so tests can replace it. interface Deps { fetchGreeting(count: number): Promise; render(view: View): void; } class Counter extends Extent { // inputs: updated only from actions (UI events, timer callbacks, network callbacks) pressed = this.moment(); resetFired = this.moment(); greetingArrived = this.moment<{ token: number; text: string | null }>(); // state: each supplied by exactly one behavior below count = this.state(0); activeRequest = this.state(null); greeting = this.state(null); // plain bookkeeping, not reactive private timer: ReturnType | undefined; private nextToken = 1; disposed = false; constructor(graph: Graph, private readonly deps: Deps) { super(graph); // one owner for count, reacting to two inputs with a fixed priority this.behavior() .demands(this.pressed, this.resetFired) .supplies(this.count) .runs((ext) => { if (ext.resetFired.justUpdated) ext.count.update(0); else if (ext.pressed.justUpdated) ext.count.update(ext.count.value + 1); }); // timer tied to an input: restart on every press this.behavior() .demands(this.pressed) .runs((ext) => { ext.sideEffect(() => { clearTimeout(this.timer); this.timer = setTimeout(() => { this.timer = undefined; if (!this.disposed) ext.resetFired.updateWithAction(); }, 1000); }); }); // async request per count, with a token so a stale reply is ignored this.behavior() .demands(this.count, this.greetingArrived) .supplies(this.activeRequest, this.greeting) .runs((ext) => { if (ext.count.justUpdated) { const token = this.nextToken++; const count = ext.count.value; ext.activeRequest.update(token); ext.sideEffect(() => { const arrived = (text: string | null) => { if (!this.disposed) ext.greetingArrived.updateWithAction({ token, text }); }; deps.fetchGreeting(count).then(arrived, () => arrived(null)); }); } const reply = ext.greetingArrived.value; if (ext.greetingArrived.justUpdated && reply !== undefined && reply.token === ext.activeRequest.value) { ext.activeRequest.update(null); ext.greeting.update(reply.text); } }); // all rendering from one behavior; addedToGraph gives the first render this.behavior() .demands(this.addedToGraph, this.count, this.greeting, this.activeRequest) .runs((ext) => { const view = { count: ext.count.value, greeting: ext.greeting.value, loading: ext.activeRequest.value !== null }; ext.sideEffect(() => deps.render(view)); }); } dispose(): void { this.disposed = true; clearTimeout(this.timer); this.timer = undefined; this.removeFromGraphWithAction(ExtentRemoveStrategy.containedLifetimes); } } const graph = new Graph(); const counter = new Counter(graph, { fetchGreeting: (n) => fetch(`/greeting?n=${n}`).then((r) => r.text()), render: (view) => { /* update the DOM, or hand the view to your UI framework */ }, }); counter.addToGraphWithAction(); button.addEventListener("click", () => counter.pressed.updateWithAction()); ``` -------------------------------- ### Manual formula dependencies in spreadsheet Source: https://github.com/yahoo/bgjs/blob/master/README.md An example of a spreadsheet formula where the user must manually specify dependent formulas to run. The order matters: if B1 later depends on C1, the calls become incorrect and C1 must run first. ```excel A1 := 1 / 1000 runFormulaFor(B1) runFormulaFor(C1) ``` -------------------------------- ### Behavior Graph Counter with moments, state, and side effects Source: https://github.com/yahoo/bgjs/blob/master/README.md Shows a Behavior Graph Counter example: an Extent with moments for increment and reset, a stateful count, and two behaviors that declare their demands and supplies. The first behavior updates count when either input changes, and the second updates the DOM whenever count changes. An outside click listener triggers the increment moment; note that reset has no external trigger in this snippet. ```typescript import { Graph, Extent } from "behavior-graph"; class Counter extends Extent { increment = this.moment(); // something happened (an input) reset = this.moment(); count = this.state(0); // a value that persists constructor(graph: Graph) { super(graph); // the rule for count: what it reads, what it writes, and how this.behavior() .demands(this.increment, this.reset) .supplies(this.count) .runs((ext) => { if (ext.reset.justUpdated) ext.count.update(0); else if (ext.increment.justUpdated) ext.count.update(ext.count.value + 1); }); // the display: runs whenever count changes, after count's rule has run this.behavior() .demands(this.count) .runs((ext) => { const text = String(ext.count.value); ext.sideEffect(() => { document.querySelector("#count")!.textContent = text; }); }); } } const graph = new Graph(); const counter = new Counter(graph); counter.addToGraphWithAction(); button.addEventListener("click", () => counter.increment.updateWithAction()); ``` -------------------------------- ### Guard stale async replies with a token and AbortController Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Handles stale async responses by storing a token in a State and checking it in the callback. It cancels superseded requests with an AbortController but still verifies the token because a cancelled request can still call back. Use this pattern to avoid applying outdated results when new searches start. ```TypeScript searchRequested = this.moment(); searchReply = this.moment<{ token: number; results: string[] }>(); searchToken = this.state(null); results = this.state([]); this.behavior() .demands(this.searchRequested, this.searchReply) .supplies(this.searchToken, this.results) .runs((ext) => { if (ext.searchRequested.justUpdated) { const token = ++this.tokenCounter; const query = ext.searchRequested.value!; ext.searchToken.update(token); ext.sideEffect(() => { this.searchAbort?.abort(); this.searchAbort = new AbortController(); this.api.search(query, this.searchAbort.signal).then( (r) => { if (!this.disposed) ext.searchReply.updateWithAction({ token, results: r }); }, () => { if (!this.disposed) ext.searchReply.updateWithAction({ token, results: [] }); }, ); }); } const reply = ext.searchReply.value; if (ext.searchReply.justUpdated && reply && reply.token === ext.searchToken.value) { ext.searchToken.update(null); ext.results.update(reply.results); } }); ``` -------------------------------- ### Extent class and ExtentRemoveStrategy enum Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Extent API for managing state, resources, behaviors, and lifetimes. addToGraph and removeFromGraph only run inside an event. addChildLifetime must be called before adding the child. unifyLifetime requires both extents to be added in the same event. Resources declared as class fields get their field name as debugName automatically. ```ts class Extent { constructor(graph: Graph); graph: Graph; debugName: string | undefined; addedToGraph: State; // false until added; justUpdated in the adding event addedToGraphWhen: number | null; // event sequence of the add, null when not in graph state(initialState: T, name?: string): State; moment(name?: string): Moment; resource(name?: string): Resource; // no value; used for ordering and as a link anchor behavior(): BehaviorBuilder; action(block: (ext: this) => void, debugName?: string): void; sideEffect(block: (ext: this) => void, debugName?: string): void; addToGraph(): void; // only inside an event (action or behavior) addToGraphWithAction(debugName?: string): void; removeFromGraph(strategy?: ExtentRemoveStrategy): void; // only inside an event removeFromGraphWithAction(strategy?: ExtentRemoveStrategy, debugName?: string): void; addChildLifetime(child: Extent): void; // call before adding the child to the graph unifyLifetime(other: Extent): void; // both must then be added in the same event subscribeToJustUpdated(resources: Resource[], callback: (ext: this) => void): () => void; } enum ExtentRemoveStrategy { extentOnly, containedLifetimes } ``` -------------------------------- ### Manual function call sequencing in onLoginClick Source: https://github.com/yahoo/bgjs/blob/master/README.md Shows the manual sequencing of three dependent functions inside a click handler, illustrating the problem that Behavior Graph solves. The functions are called in strict order, and the dependency relationships are only implicit in the call order. ```javascript function onLoginClick() { validateFields(); networkLogin(); updateUI(); } ``` -------------------------------- ### Load behavior-graph as classic script with global bg Source: https://github.com/yahoo/bgjs/blob/master/README.md Load behavior-graph as a classic script, which defines a global `bg`. Use this in a browser without a build step when you prefer a global variable rather than ES modules. ```html ``` -------------------------------- ### Moment Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md A resource that fires an event with an optional payload. Extends Resource. ```APIDOC ## Moment ### Description A resource that fires an event with an optional payload. Extends Resource. ### Methods #### update(value?: T): void - **Parameters**: `value` (T) - Optional - The payload to set. - **Description**: Updates the moment. Can be called inside an action or a supplying behavior. #### updateWithAction(value?: T, debugName?: string): void - **Parameters**: - `value` (T) - Optional - The payload to set. - `debugName` (string) - Optional - Debug name for the action. - **Description**: Wraps the update in a graph action. #### get justUpdated(): boolean - **Returns**: `boolean` - True only during the event it fired. - **Description**: Indicates whether the moment was updated in the current event. #### get value(): T | undefined - **Returns**: `T | undefined` - The payload, or undefined after the event. - **Description**: Returns the payload of the moment. #### get event(): GraphEvent | null - **Returns**: `GraphEvent | null` - The event when it last fired, or null if never fired. - **Description**: Returns the last event that fired this moment. #### justUpdatedTo(value: T): boolean - **Parameters**: `value` (T) - Required - The value to compare. - **Returns**: `boolean` - True if justUpdated and value equals the payload. - **Description**: Checks if the moment was just updated to the given value. ``` -------------------------------- ### Load behavior-graph as ES module from CDN Source: https://github.com/yahoo/bgjs/blob/master/README.md Load behavior-graph from a CDN (jsDelivr) as an ES module. Use this in a browser without a build step. The import uses the version 2 URL. ```html ``` -------------------------------- ### BehaviorBuilder Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Fluent builder for defining behaviors. Use demands, supplies, dynamicDemands, dynamicSupplies, and runs to configure a behavior. ```APIDOC ## BehaviorBuilder ### Description Fluent builder for defining behaviors. Use demands, supplies, dynamicDemands, dynamicSupplies, and runs to configure a behavior. ### Methods #### demands(...d: Demandable[]) - **Parameters**: `d` (Demandable[]) - Required - Resources, States, Moments, resource.order, or state.trace. - **Description**: Adds demands to the behavior. Can be called multiple times to add more. #### supplies(...s: Resource[]) - **Parameters**: `s` (Resource[]) - Required - Resources supplied by the behavior. - **Description**: Adds supplies to the behavior. Can be called multiple times to add more. #### dynamicDemands(switches: Demandable[], links: (ext: this) => (Demandable | undefined)[] | null, relinkingOrder?: RelinkingOrder) - **Parameters**: - `switches` (Demandable[]) - Required - Switch demands that trigger relinking. - `links` (function) - Required - Function returning an array of extra demands (or null). Undefined entries are dropped. - `relinkingOrder` (RelinkingOrder) - Optional - Default `RelinkingOrder.relinkingOrderPrior`. - **Description**: Sets dynamic demands. When any switch updates, links is re-run and its result becomes the behavior's extra demands for the rest of the event. Can only be called once per behavior. #### dynamicSupplies(switches: Demandable[], links: (ext: this) => (Resource | undefined)[] | null, relinkingOrder?: RelinkingOrder) - **Parameters**: - `switches` (Demandable[]) - Required - Switch demands that trigger relinking. - `links` (function) - Required - Function returning an array of extra supplies (or null). Undefined entries are dropped. - `relinkingOrder` (RelinkingOrder) - Optional - Default `RelinkingOrder.relinkingOrderPrior`. - **Description**: Sets dynamic supplies. When any switch updates, links is re-run and its result becomes the behavior's extra supplies for the rest of the event. Can only be called once per behavior. #### runs(block: (ext: this) => void): Behavior - **Parameters**: `block` (function) - Required - The behavior's execution block. - **Returns**: `Behavior` - The created behavior. - **Description**: Defines the behavior's execution logic. Inside the block, you may read `.value`, `.event`, `.justUpdated` on demanded or supplied resources, and call `.update()` only on supplied resources. ### Enum RelinkingOrder - `relinkingOrderPrior` - Default. Relink happens before the behavior runs. - `relinkingOrderSubsequent` - Use when the behavior itself supplies a switch to avoid cycles. ### Rules - A behavior runs only if at least one demanded resource updated in the event. No demands means it never runs; a behavior with only `.trace` demands throws. - `.demands()` and `.supplies()` can be called multiple times to add to the list. `.dynamicDemands` and `.dynamicSupplies` may each be called once; a second call throws. - `resource.order` is an ordering-only demand: the behavior is sorted after that resource's supplier and may read it, but does not run when it updates. It is still an edge and can form cycles. - `state.trace` is a trace demand: the behavior may read `state.traceValue` but not `.value`. It is not an edge and cannot form cycles. With `graph.validateTraceDemands` on (default), `traceValue` and `traceEvent` may be read only on states the behavior supplies, demands (plain or `.order`), or declares with `.trace`. - `dynamicDemands` and `dynamicSupplies` relink when any switch updates. With default `relinkingOrderPrior`, relink happens before the behavior runs. Use `relinkingOrderSubsequent` when the behavior supplies a switch. `undefined` entries in the returned array are dropped. ``` -------------------------------- ### Resource Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Base class for resources in the graph. Provides introspection and ordering demand. ```APIDOC ## Resource ### Description Base class for resources in the graph. Provides introspection and ordering demand. ### Properties - `debugName` (string | null) - Debug name for the resource. - `extent` (Extent) - The extent of the resource. - `graph` (Graph) - The graph this resource belongs to. - `suppliedBy` (Behavior | null) - The behavior that supplies this resource, or null if none. - `subsequents` (Set) - Behaviors that demand this resource. ### Methods #### get order(): Demandable - **Returns**: `Demandable` - An ordering-only demand for this resource. - **Description**: Returns an ordering demand that allows reading but does not trigger the behavior to run when this resource updates. #### get justUpdated(): boolean - **Returns**: `boolean` - Always false on a plain Resource. - **Description**: Indicates whether the resource was updated in the current event. Always false for plain Resource. ``` -------------------------------- ### Pre-decider with trace demands and intent resource Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Implements a pre-decider that runs before an existing owner. Uses `.trace` demands to read previous values (`traceValue`) without forming an edge, enabling an upstream behavior to supply an intent resource (`effectiveRequest`) that the owner must demand instead of the raw input. ```ts // upstream: decides what the owner should treat as the user's request effectiveRequest = this.state<"start" | "hold" | "stop" | null>(null); this.behavior() .demands(this.startRequested, this.lockEngaged, this.lockReleased, this.phase.trace, this.locked.trace) .supplies(this.effectiveRequest) .runs((ext) => { const wasRunning = ext.phase.traceValue === "running"; // previous event, via phase.trace if (ext.lockEngaged.justUpdated && wasRunning) ext.effectiveRequest.update("stop"); else if (ext.startRequested.justUpdated && ext.locked.traceValue) ext.effectiveRequest.update("hold"); else if (ext.startRequested.justUpdated) ext.effectiveRequest.update("start"); }); // the owner of `phase` demands effectiveRequest, never startRequested directly ``` -------------------------------- ### Handle joint reactions with multiple demands and priority checks Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Handles multiple inputs and derived changes in one event. Demands several moments and a state, checks `justUpdated` on each in priority order, and updates a supplied `phase` state. Useful for coordinating complex UI state transitions where all updates in an action are simultaneous. ```ts this.behavior() .demands(this.cancelPressed, this.submitPressed, this.formValid, this.mode) .supplies(this.phase) .runs((ext) => { if (ext.cancelPressed.justUpdated) ext.phase.update("idle"); // highest priority else if (ext.submitPressed.justUpdated && ext.formValid.value) ext.phase.update("sending"); else if (ext.mode.justUpdatedTo("locked") && ext.phase.value !== "idle") ext.phase.update("idle"); }); ``` -------------------------------- ### Trace event changes with a behavior in the root extent Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Add this tracing behavior in the root extent to log event by event what changed. It demands resources of interest and logs from a side effect, supplying nothing so it does not perturb ordering. ```typescript this.behavior() .demands(this.pressed, this.count, this.greeting, this.activeRequest) .runs((ext) => { const seq = ext.graph.currentEvent!.sequence; const changed = [ext.pressed, ext.count, ext.greeting, ext.activeRequest] .filter((r) => r.justUpdated) .map((r) => r.toString()); ext.sideEffect(() => console.log(`event ${seq}:`, changed.join(", "))); }); ``` -------------------------------- ### Extent class Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md The Extent class provides methods for creating states, moments, resources, and behaviors, and for managing graph lifecycle and lifetimes. ```APIDOC ## Extent class ### Description The Extent class provides methods for creating states, moments, resources, and behaviors, and for managing graph lifecycle and lifetimes. ### Constructor `new Extent(graph: Graph)` ### Properties - `graph: Graph` - The graph this extent belongs to. - `debugName: string | undefined` - The debug name of the extent. - `addedToGraph: State` - False until added; justUpdated in the adding event. - `addedToGraphWhen: number | null` - Event sequence of the add, null when not in graph. ### Methods #### state(initialState: T, name?: string): State - **Description**: Creates a state with the given initial value. - **Parameters**: - `initialState` (T) - Required - The initial state value. - `name` (string) - Optional - A name for the state. - **Returns**: A State instance. #### moment(name?: string): Moment - **Description**: Creates a moment. - **Parameters**: - `name` (string) - Optional - A name for the moment. - **Returns**: A Moment instance. #### resource(name?: string): Resource - **Description**: Creates a resource with no value; used for ordering and as a link anchor. - **Parameters**: - `name` (string) - Optional - A name for the resource. - **Returns**: A Resource instance. #### behavior(): BehaviorBuilder - **Description**: Returns a behavior builder for this extent. - **Returns**: A BehaviorBuilder instance. #### action(block: (ext: this) => void, debugName?: string): void - **Description**: Runs an action within the extent's graph. - **Parameters**: - `block` (function) - Required - The block to execute, receives the extent as argument. - `debugName` (string) - Optional - A name for debugging. #### sideEffect(block: (ext: this) => void, debugName?: string): void - **Description**: Queues a side effect. - **Parameters**: - `block` (function) - Required - The effect to queue, receives the extent as argument. - `debugName` (string) - Optional - A name for debugging. #### addToGraph(): void - **Description**: Adds the extent to the graph. Only inside an event (action or behavior). #### addToGraphWithAction(debugName?: string): void - **Description**: Adds the extent to the graph with an action. - **Parameters**: - `debugName` (string) - Optional - A name for debugging. #### removeFromGraph(strategy?: ExtentRemoveStrategy): void - **Description**: Removes the extent from the graph. Only inside an event. - **Parameters**: - `strategy` (ExtentRemoveStrategy) - Optional - The removal strategy. #### removeFromGraphWithAction(strategy?: ExtentRemoveStrategy, debugName?: string): void - **Description**: Removes the extent from the graph with an action. - **Parameters**: - `strategy` (ExtentRemoveStrategy) - Optional - The removal strategy. - `debugName` (string) - Optional - A name for debugging. #### addChildLifetime(child: Extent): void - **Description**: Adds a child lifetime. Call before adding the child to the graph. - **Parameters**: - `child` (Extent) - Required - The child extent. #### unifyLifetime(other: Extent): void - **Description**: Unifies lifetimes. Both must then be added in the same event. - **Parameters**: - `other` (Extent) - Required - The other extent. #### subscribeToJustUpdated(resources: Resource[], callback: (ext: this) => void): () => void - **Description**: Subscribes to updates of the given resources. Returns an unsubscribe function. - **Parameters**: - `resources` (Resource[]) - Required - The resources to watch. - `callback` (function) - Required - Called when any of the resources are just updated, receives the extent. - **Returns**: A function to unsubscribe. ### Enum: ExtentRemoveStrategy - `extentOnly` - Remove only the extent. - `containedLifetimes` - Remove the extent and its contained lifetimes. ``` -------------------------------- ### Import behavior-graph API Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Import the behavior-graph API types and classes for use in TypeScript or JavaScript code. This is an explicit import statement showing the module's public symbols. ```ts import { Graph, Extent, State, Moment, Resource, Behavior, GraphEvent, ExtentRemoveStrategy, RelinkingOrder } from "behavior-graph"; ``` -------------------------------- ### State Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md A resource that holds a persistent value. Extends Resource. ```APIDOC ## State ### Description A resource that holds a persistent value. Extends Resource. ### Methods #### update(newValue: T): void - **Parameters**: `newValue` (T) - Required - The new value. - **Description**: Updates the state. No-op if newValue === current value. #### updateForce(newValue: T): void - **Parameters**: `newValue` (T) - Required - The new value. - **Description**: Updates the state even if equal, forcing justUpdated. #### updateWithAction(newValue: T, debugName?: string): void - **Parameters**: - `newValue` (T) - Required - The new value. - `debugName` (string) - Optional - Debug name for the action. - **Description**: Wraps the update in a graph action. #### get value(): T - **Returns**: `T` - The current value. - **Description**: Returns the current value of the state. #### get event(): GraphEvent - **Returns**: `GraphEvent` - The event of the last change; sequence 0 initially. - **Description**: Returns the event of the last change. #### get justUpdated(): boolean - **Returns**: `boolean` - True if the state was updated in the current event. - **Description**: Indicates whether the state was updated in the current event. #### get trace(): Demandable - **Returns**: `Demandable` - A trace demand for this state. - **Description**: Returns a trace demand that permits reading traceValue but is not an edge. #### get traceValue(): T - **Returns**: `T` - The value at the start of the current event. - **Description**: Returns the value at the start of the current event. Requires a demand or .trace. #### get traceEvent(): GraphEvent - **Returns**: `GraphEvent` - The event at the start of the current event. - **Description**: Returns the event at the start of the current event. #### justUpdatedTo(to: T): boolean - **Parameters**: `to` (T) - Required - The value to compare. - **Returns**: `boolean` - True if justUpdated and value equals to. - **Description**: Checks if the state was just updated to the given value. #### justUpdatedFrom(from: T): boolean - **Parameters**: `from` (T) - Required - The value to compare. - **Returns**: `boolean` - True if justUpdated and previous value equals from. - **Description**: Checks if the state was just updated from the given value. #### justUpdatedToFrom(to: T, from: T): boolean - **Parameters**: - `to` (T) - Required - The new value. - `from` (T) - Required - The previous value. - **Returns**: `boolean` - True if justUpdated and value equals to and previous value equals from. - **Description**: Checks if the state was just updated from one value to another. ``` -------------------------------- ### Manage child collections with addChildLifetime and dynamicDemands in TypeScript Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Shows how to model a collection whose membership changes: the parent owns a State and adds/removes children inside behaviors in the same event. Use ext.addChildLifetime(child) before child.addToGraph() so child behaviors can statically demand parent resources. The aggregate behavior uses dynamicDemands on the list to react to membership changes; the default relinkingOrderPrior ensures it sees new demands in the same event. The source notes that children must be removed from the graph and list in the same event and that routing an input to a child can be done by finding it in items.value. ```ts class Item extends Extent { done = this.state(false); toggle = this.moment(); constructor(graph: Graph, readonly id: string) { super(graph); this.behavior().demands(this.toggle).supplies(this.done) .runs((ext) => { ext.done.update(!ext.done.value); }); } } class List extends Extent { addRequested = this.moment(); removeRequested = this.moment(); items = this.state([]); doneCount = this.state(0); constructor(graph: Graph) { super(graph); // membership: create children inside the behavior, in the same event this.behavior() .demands(this.addRequested, this.removeRequested) .supplies(this.items) .runs((ext) => { if (ext.addRequested.justUpdated) { const item = new Item(ext.graph, ext.addRequested.value!); ext.addChildLifetime(item); item.addToGraph(); ext.items.update([...ext.items.value, item]); } if (ext.removeRequested.justUpdated) { const id = ext.removeRequested.value; const gone = ext.items.value.filter((i) => i.id === id); gone.forEach((i) => i.removeFromGraph()); ext.items.update(ext.items.value.filter((i) => i.id !== id)); } }); // aggregate: demands change whenever the list changes this.behavior() .demands(this.items) .dynamicDemands([this.items], (ext) => ext.items.value.map((i) => i.done)) .supplies(this.doneCount) .runs((ext) => { ext.doneCount.update(ext.items.value.filter((i) => i.done.value).length); }); } } ``` -------------------------------- ### Graph and GraphEvent class definitions Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Core Graph API. Action runs synchronously; sideEffect queues an effect. currentEvent is non-null while an event runs. dateProvider can be replaced for time control in tests. validateLifetimes and validateTraceDemands default to true. ```ts class Graph { constructor(); action(block: () => void, debugName?: string): void; // open an event, run block, drain sideEffect(block: () => void, debugName?: string): void; // queue an effect (no extent arg) currentEvent: GraphEvent | null; // non-null while an event is running lastEvent: GraphEvent; // last completed event; starts at sequence 0 currentBehavior: Behavior | null; // non-null while a behavior block is running dateProvider: { now(): Date }; // stamps events; replace it to control time in tests validateLifetimes: boolean; // default true; see lifetimes below validateTraceDemands: boolean; // default true: traceValue needs a demand or .trace debugHere(): string; // text dump of current event/behavior subscribeToJustUpdated(resources: Resource[], callback: () => void): () => void; } class GraphEvent { sequence: number; timestamp: Date; } ``` -------------------------------- ### Graph class Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md The Graph class is the central event loop. It provides methods to run actions and side effects, access current and last events, and manage validation and time. ```APIDOC ## Graph class ### Description The Graph class is the central event loop. It provides methods to run actions and side effects, access current and last events, and manage validation and time. ### Constructor `new Graph()` ### Methods #### action(block: () => void, debugName?: string): void - **Description**: Opens an event, runs the block, and drains side effects. Synchronous: when it returns, the event and its side effects are complete. - **Parameters**: - `block` (function) - Required - The block to execute within the event. - `debugName` (string) - Optional - A name for debugging. #### sideEffect(block: () => void, debugName?: string): void - **Description**: Queues an effect to run after the current event. Does not take an extent argument. - **Parameters**: - `block` (function) - Required - The effect to queue. - `debugName` (string) - Optional - A name for debugging. #### subscribeToJustUpdated(resources: Resource[], callback: () => void): () => void - **Description**: Subscribes to updates of the given resources. Returns an unsubscribe function. - **Parameters**: - `resources` (Resource[]) - Required - The resources to watch. - `callback` (function) - Required - Called when any of the resources are just updated. - **Returns**: A function to unsubscribe. ### Properties - `currentEvent: GraphEvent | null` - Non-null while an event is running. - `lastEvent: GraphEvent` - Last completed event; starts at sequence 0. - `currentBehavior: Behavior | null` - Non-null while a behavior block is running. - `dateProvider: { now(): Date }` - Stamps events; replace it to control time in tests. - `validateLifetimes: boolean` - Default true; see lifetimes below. - `validateTraceDemands: boolean` - Default true: traceValue needs a demand or .trace. ### Methods (continued) #### debugHere(): string - **Description**: Returns a text dump of the current event/behavior. ### GraphEvent class - `sequence: number` - The sequence number of the event. - `timestamp: Date` - The timestamp of the event. ``` -------------------------------- ### Single behavior for multiple writers to one state Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Centralizes all writes to a single piece of state into one supplying behavior. Each writer becomes an input moment that the behavior demands, and conflicts are resolved in priority order. Use when multiple parts of the system need to update the same state. ```ts this.behavior() .demands(this.incrementRequested, this.setRequested, this.resetRequested) .supplies(this.count) .runs((ext) => { if (ext.resetRequested.justUpdated) ext.count.update(0); else if (ext.setRequested.justUpdated) ext.count.update(ext.setRequested.value!); else if (ext.incrementRequested.justUpdated) ext.count.update(ext.count.value + 1); }); ``` -------------------------------- ### Schedule timer with sideEffect and arm on transitions in TypeScript Source: https://github.com/yahoo/bgjs/blob/master/AGENT_GUIDE.md Implements a timer tied to a condition: a behavior decides when to arm or disarm, and a side effect schedules the timeout. The timeout fires as a moment (updateWithAction only if not disposed). The source notes to arm and disarm on transitions, not on every event, and to always clear timers in teardown. When multiple rules can influence the timer, let a tri-state armFor: State drive a dedicated timer behavior: a number arms for that many ms, null disarms, undefined leaves the running timer alone. ```ts armed = this.state(false); // derived by some behavior timeout = this.moment(); this.behavior() .demands(this.armed) .runs((ext) => { if (ext.armed.justUpdatedTo(true)) { ext.sideEffect(() => { this.timerId = setTimeout(() => { this.timerId = undefined; if (!this.disposed) ext.timeout.updateWithAction(); }, 5000); }); } else if (ext.armed.justUpdatedTo(false)) { ext.sideEffect(() => { clearTimeout(this.timerId); this.timerId = undefined; }); } }); ```