### Install Dependencies Source: https://github.com/maurice/yay-machine/blob/main/packages/site/README.md Run this command from the project root to install all necessary dependencies. ```bash npm install ``` -------------------------------- ### Install Dependencies and Run All Scripts Source: https://github.com/maurice/yay-machine/blob/main/development.md Use 'npm install' to set up project dependencies. 'npm run all' executes all defined scripts, useful for a comprehensive check. ```sh npm install # install dependencies npm run all # run all scripts - check, test, build ``` -------------------------------- ### Tape Machine Usage Example Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/tape/index.mdx Demonstrates how to instantiate and use the tape machine with the mock hardware interface. Shows starting the machine, sending events, and simulating external hardware events. ```typescript import { tapeMachine } from "./tapeMachine"; import { HardwareInterface } from "./HardwareInterface"; // Instantiate the hardware interface const hardwareInterface = new HardwareInterface(); // Create the tape machine instance const machine = tapeMachine.createInstance({ context: { hardwareInterface, }, }); // Start the machine machine.start(); // Send events to the machine machine.send("PLAY"); // Simulate external hardware events hardwareInterface.simulateStart(); setTimeout(() => { hardwareInterface.simulateEnd(); }, 1000); setTimeout(() => { machine.send("STOP"); machine.stop(); }, 2000); ``` -------------------------------- ### Start Local Dev Server Source: https://github.com/maurice/yay-machine/blob/main/packages/site/README.md Starts the local development server for the documentation site. Accessible at `localhost:4321`. ```bash npm run site:dev ``` -------------------------------- ### Start a machine instance with `machine.start()` Source: https://context7.com/maurice/yay-machine/llms.txt Starts the machine, runs the optional `onStart()` side-effect, enters the initial state, and processes any immediate transitions. Returns `this` for chaining. Throws if already running. ```typescript import { defineMachine } from "yay-machine"; const machine = defineMachine< { readonly name: "idle" | "running" }, { readonly type: "STOP" } >({ initialState: { name: "idle" }, onStart: ({ state, send }) => { console.log("machine started in state:", state.name); // Return an optional cleanup function return () => console.log("machine onStart cleaned up"); }, states: { idle: { always: { to: "running" }, // immediate transition on entry }, running: { on: { STOP: { to: "idle" } }, }, }, }); const instance = machine.newInstance(); instance.start(); // logs: "machine started in state: idle" console.log(instance.state.name); // "running" (immediate transition taken) ``` -------------------------------- ### Create and Start State-Machine Instance Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/quick-start/index.mdx Instantiate and start a state-machine with initial state data and subscribe to its events. Ensure all necessary APIs are imported and provided to the initial state. ```typescript import { cardReader, cashDispenser, keypad, bank } from "./apis"; const atm = atmMachine .newInstance({ initialState: { name: "waitingForCard", cardReader, cashDispenser, keypad, bank, cardNumber: undefined!, pin: undefined!, withdrawalAmount: 0, transactionId: undefined!, withdrawalAttempts: 0, message: "", }, }) .start(); const unsubscribe = atm.subscribe(({ state, event }) => { console.log("machine state", state, event); }); ``` -------------------------------- ### Price Machine Usage TypeScript Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/stock-tickers/index.mdx Example of how to use the price machine. Instantiate and start a service for a given stock symbol. ```typescript import { createPriceMachine } from "./priceMachine"; const applePriceService = createPriceMachine("AAPL"); applePriceService.subscribe((state) => { console.log(`AAPL price state: ${state.value}`); if (state.matches("live")) { console.log(` Price: ${state.context.price}`); console.log(` Valid until: ${new Date(state.context.validUntil!)}\n`); } }); // Simulate receiving a tick applePriceService.send({ type: "TICK", payload: { price: 150.5, timeValid: 5000 } }); // Simulate receiving another tick before the first one becomes stale setTimeout(() => { applePriceService.send({ type: "TICK", payload: { price: 151.0, timeValid: 7000 } }); }, 2000); // After some time, the price will become stale if no new ticks arrive ``` -------------------------------- ### Documentation Site Development Source: https://github.com/maurice/yay-machine/blob/main/development.md Commands to start a local development server for the documentation site or to build it for deployment. ```sh npm run site:dev # site dev server (watch mode) npm run site:build # static build for deployment ``` -------------------------------- ### Querying Homogenous State Data Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/state.mdx Demonstrates how to access and use the state data after starting a machine instance. This example shows conditional logic based on the connection state. ```typescript const connection = connectionMachine.newInstance().start(); // ... use the machine ... if (connection.state.name === "connected") { console.log( "It took %s milliseconds to establish the connection, and its uptime is %s millis", connection.state.connectionEstablishedAt - connection.state.connectingStartedAt, Date.now() - connection.state.connectionEstablishedAt, ); } ``` -------------------------------- ### machine.start() Source: https://context7.com/maurice/yay-machine/llms.txt Starts the machine instance, runs the optional onStart side-effect, enters the initial state, and processes any immediate transitions. Returns the instance for chaining and throws an error if the machine is already running. ```APIDOC ## `machine.start()` — Start a machine instance Starts the machine, runs the optional `onStart()` side-effect, enters the initial state (running its optional `onEnter()` side-effect), and processes any immediate (`always`) transitions. Returns `this` for chaining. Throws if already running. ### Example Usage ```typescript import { defineMachine } from "yay-machine"; const machine = defineMachine({ initialState: { name: "idle" }, onStart: ({ state, send }) => { console.log("machine started in state:", state.name); return () => console.log("machine onStart cleaned up"); }, states: { idle: { always: { to: "running" }, // immediate transition on entry }, running: { on: { STOP: { to: "idle" } }, }, }, }); const instance = machine.newInstance(); instance.start(); console.log(instance.state.name); // "running" ``` ``` -------------------------------- ### Install yay-machine with npm Source: https://github.com/maurice/yay-machine/blob/main/README.md Use npm to add the yay-machine library to your project dependencies. ```sh npm add yay-machine ``` -------------------------------- ### Get Astro CLI Help Source: https://github.com/maurice/yay-machine/blob/main/packages/site/README.md Displays help information for the Astro CLI commands when run from the `packages/site` directory. ```bash npm run astro -- --help ``` -------------------------------- ### Ticker Machine Usage TypeScript Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/stock-tickers/index.mdx Example of how to use the ticker machine. Instantiate and start a service to manage multiple stock tickers. ```typescript import { interpret } from "@yay-machine/core"; import { tickerMachine, TickerEvent } from "./tickerMachine"; const tickerService = interpret(tickerMachine).start(); tickerService.subscribe((state) => { console.log(`Ticker state: ${state.value}`); console.log("Current symbols:", Object.keys(state.context.symbols)); }); // Add some tickers tickerService.send({ type: "ADD_TICKER", payload: { symbol: "AAPL", timeValid: 5000 } }); tickerService.send({ type: "ADD_TICKER", payload: { symbol: "GOOG", timeValid: 7000 } }); // Simulate WebSocket connection established // In a real scenario, this would be triggered by the WebSocket 'onopen' event tickerService.send({ type: "CONNECTED" }); // Simulate receiving ticks from WebSocket setTimeout(() => { // @ts-ignore tickerService.send({ type: "TICK", symbol: "AAPL", payload: { price: 155.2, timeValid: 5000 } }); // @ts-ignore tickerService.send({ type: "TICK", symbol: "GOOG", payload: { price: 2800.1, timeValid: 7000 } }); }, 1000); // Simulate removing a ticker setTimeout(() => { tickerService.send({ type: "REMOVE_TICKER", payload: { symbol: "AAPL" } }); }, 6000); // Simulate disconnection // setTimeout(() => { // tickerService.send({ type: "DISCONNECTED" }); // }, 10000); ``` -------------------------------- ### Connection Machine Usage Example Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/transitions.mdx Demonstrates how to use the connection machine, including creating a transport instance and sending events to the machine. This shows practical application of the defined machine. ```typescript import { createMachine } from "@yay-machine/core"; import { connectionMachine, State, Event } from "./connectionMachine"; import { Transport } from "./Transport"; const transport = new Transport("wss://example.com/socket"); const machine = createMachine(connectionMachine, { actions: { // Actions to perform when entering/exiting states or on specific events onCONNECT: () => { transport.connect().catch((error) => { // Handle connection errors by sending an ERROR event machine.send({ type: "ERROR", payload: { reason: error.message } }); }); }, onCONNECTED: () => { console.log("Machine is now connected."); }, onERROR: (event) => { console.error("Received ERROR event:", event.payload); if (event.payload?.reason === "Max connection attempts reached") { // Send a specific event for max attempts reached machine.send({ type: "ERROR", payload: { maxAttemptsReached: true } }); } }, onDISCONNECTED: () => { transport.disconnect(); }, }, }); // Example of sending events to the machine // Initial transition from disconnected to connecting machine.send("CONNECT"); // Simulate a successful connection after some time setTimeout(() => { if (machine.getState() === "connecting") { machine.send({ type: "CONNECTED" }); } }, 1000); // Simulate sending data when connected setTimeout(() => { if (machine.getState() === "connected") { transport.send({ message: "Hello!" }); } }, 2000); // Simulate a disconnection setTimeout(() => { if (machine.getState() === "connected") { machine.send("DISCONNECTED"); } }, 3000); ``` -------------------------------- ### Health Machine Usage Example (TypeScript) Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/health/index.mdx Demonstrates how to use the health machine in an application. This includes creating an instance, sending events, and subscribing to state changes. ```typescript import { healthMachine } from "@yay-machine/example-machines/src/health/healthMachine"; // Example usage of the health machine async function runHealthExample() { // Create a new instance of the health machine const instance = healthMachine.createInstance({ // Initial data for the machine instance data: { health: 100 } }); // Subscribe to state changes instance.subscribe(state => { console.log(`Current state: ${state.name}, Health: ${state.data.health}`); if (state.data.invincibilityStarted) { console.log(`Invincible since: ${new Date(state.data.invincibilityStarted).toLocaleTimeString()}`); } }); // Send events to the machine console.log("\n--- Initial State ---"); await instance.send("DAMAGE"); // Health: 90 await instance.send("DAMAGE"); // Health: 80 await instance.send("FIRST_AID"); // Health: 100 console.log("\n--- Entering Critical Health ---"); await instance.send("DAMAGE"); // Health: 90 await instance.send("DAMAGE"); // Health: 80 await instance.send("DAMAGE"); // Health: 70 await instance.send("DAMAGE"); // Health: 60 await instance.send("DAMAGE"); // Health: 50 (surviving) await instance.send("DAMAGE"); // Health: 40 await instance.send("DAMAGE"); // Health: 30 await instance.send("DAMAGE"); // Health: 20 (critical) await instance.send("DAMAGE"); // Health: 10 await instance.send("DAMAGE"); // Health: 0 (expired) console.log("\n--- Invincibility ---"); // Reset health to demonstrate invincibility instance.reset({ data: { health: 50 } }); await instance.send("GOD_LIKE"); // Enters invincible state // Wait for invincibility to expire (10 seconds) await new Promise(resolve => setTimeout(resolve, 11000)); console.log("\n--- After Invincibility ---"); await instance.send("DAMAGE"); // Health: 40 (returned to checkHealth) } runHealthExample(); ``` -------------------------------- ### XState Machine with Homogenous Context Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/articles/vs-xstate.mdx This XState example demonstrates a machine with a homogenous context object that remains consistent across states. The output shows the initial state and its associated context. ```typescript const feedbackMachine = createMachine({ id: "feedback", initial: "question", context: { feedback: "", }, states: { question: { meta: { question: "How was your experience?", }, }, }, }); const actor = createActor(feedbackMachine); actor.start(); console.log(actor.getSnapshot()); // Logs an object containing: // { // value: 'question', // context: { // feedback: '' // }, // meta: { // 'feedback.question': { // question: 'How was your experience?' // } // } // } ``` -------------------------------- ### Operate Heater Machine Instance Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/machines.mdx Demonstrates creating and interacting with a heater machine instance at run-time. Includes starting, sending events, and subscribing to state changes. ```typescript import { heaterMachine } from "@yay-machine/example-machines/src/heater/heaterMachine.ts?raw"; const heater = heaterMachine.createInstance(); heater.subscribe(({ value, context }) => { console.log("Heater state changed:", value, context); }); heater.send("heat", { temperature: 25 }); heater.send("cool", { temperature: 18 }); heater.send("turnOff"); // Stop the machine instance heater.stop(); ``` -------------------------------- ### Create and operate state machine instances at run-time Source: https://github.com/maurice/yay-machine/blob/main/README.md Instantiate, start, and interact with a defined state machine. Includes subscribing to state changes and sending events. ```typescript import assert from "assert"; import { guessMachine } from "./guessMachine"; // create and start a new instance of the guess game machine const guess = guessMachine.newInstance().start(); assert(guess.state.name === "playing"); // subscribe to the machine's state as it changes const unsubscribe = guess.subscribe(({ state }) => { if (state.name === "guessedCorrectly") { console.log("game over: yay, we won :)"); } else if (state.name === "tooManyIncorrectGuesses") { console.log("game over: boo, we lost :("); } else { return; } unsubscribe(); }); // play a single game while (guess.state.name === "playing") { guess.send({ type: "GUESS", guess: Math.ceil(Math.random() * 10) }); } ``` -------------------------------- ### STOMP Parser Machine Usage Example Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/stomp-parser/index.mdx Demonstrates how to use the STOMP parser machine to parse a sample STOMP message. ```typescript import { extractFrames } from "./parseUtils"; // Sample STOMP message with multiple frames const rawMessage = ` SUBSCRIBE\nsub-id:sub-1\n\n\nSEND\nreceipt:message-123\ncontent-length:11\n\nHello World\x00\nMESSAGE\nmessage-id:msg-1\ncontent-length:5\n\nWorld\x00\n`; // Extract frames from the raw message const frames = extractFrames(rawMessage); // Log the parsed frames console.log(JSON.stringify(frames, null, 2)); /* Expected output: [ { "command": "SUBSCRIBE", "headers": { "sub-id": "sub-1" }, "body": "" }, { "command": "SEND", "headers": { "receipt": "message-123", "content-length": "11" }, "body": "Hello World" }, { "command": "MESSAGE", "headers": { "message-id": "msg-1", "content-length": "5" }, "body": "World" } ] */ ``` -------------------------------- ### Register Card Insert Listener on Machine Start Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/quick-start/index.mdx Sets up an event listener for card insertion when the machine starts. The listener sends a `CARD_INSERTED` event to the machine. Returns a cleanup function to remove the listener when the machine stops. ```typescript export const atmMachine = defineMachine({ initialState: { name: "waitingForCard", cardReader: undefined!, cashDispenser: undefined!, keypad: undefined!, bank: undefined!, cardNumber: undefined!, pin: undefined!, withdrawalAmount: 0, transactionId: undefined!, withdrawalAttempts: 0, message: "", }, enableCopyDataOnTransition: true, onStart: ({ state: { cardReader }, send }) => { return cardReader.addCardInsertedListener(() => send({ type: "CARD_INSERTED" }), ); }, states: { ``` -------------------------------- ### Elevator Machine Usage Example Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/elevator/index.mdx Demonstrates how to use the elevator machine by creating an instance, sending events, and subscribing to state changes. This shows a practical application of the defined state machine. ```typescript import elevatorMachine from "./elevatorMachine"; // Create an interpreter for the elevator machine const elevator = elevatorMachine.createInterpreter(); // Subscribe to state changes elevator.subscribe((state) => { console.log("State changed:", state.name, "\nData:", state.data); }); // Start the machine elevator.start(); // Simulate elevator usage console.log("Summoning elevator to floor 5..."); elevator.send("VISIT_FLOOR", { floor: 5 }); setTimeout(() => { console.log("Passenger inside requests floor 2..."); elevator.send("VISIT_FLOOR", { floor: 2 }); }, 10000); setTimeout(() => { console.log("Passenger inside requests floor 7..."); elevator.send("VISIT_FLOOR", { floor: 7 }); }, 15000); setTimeout(() => { console.log("Summoning elevator to floor 1 (current floor)..."); elevator.send("VISIT_FLOOR", { floor: 1 }); }, 20000); ``` -------------------------------- ### Guess Machine Usage Example Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/guess/index.mdx Demonstrates how to use the guess machine in an Astro component. It shows initializing the machine, sending events, and accessing state data to render the game interface. ```typescript import { useMachine } from "@yay-machine/react"; import { guessMachine } from "@yay-machine/example-machines/src/guess/guessMachine"; import Guess from "./Guess.astro"; const {{ '{' }} state, send {{ '}' }} = useMachine(guessMachine); const { context: { guessesLeft, lastGuess }, value: currentState, } = state; const handleGuess = (guess: number) => { send({ type: "GUESS", payload: guess }); }; const handleRestart = () => { send({ type: "RESTART" }); }; ``` -------------------------------- ### yay-machine with Heterogenous State Data Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/articles/vs-xstate.mdx This yay-machine example illustrates heterogenous state data, where different states can have distinct data structures. It shows the initial state and the state after a feedback event is processed. ```typescript interface QuestionState { readonly name: "question"; } interface FeedbackProvidedState { readonly name: "feedbackProvided"; readonly feedback: string; } interface FeedbackEvent { readonly type: "FEEDBACK"; readonly feedback: string; } const feedbackMachine = defineMachine< QuestionState | FeedbackProvidedState, FeedbackEvent >({ initialState: { name: "question" }, states: { question: { on: { FEEDBACK: { to: "feedbackProvided", data: ({ event: { feedback } }) => ({ feedback }), }, }, }, }, }); const feedback = feedbackMachine.newInstance(); feedback.start(); console.log(feedback.state); // { name: 'question' } feedback.send({ type: "FEEDBACK", feedback: "Keep up the good work!" }); console.log(feedback.state); // { name: 'feedbackProvided', feedback: 'Keep up the good work!' } ``` -------------------------------- ### Machine onStart and onStop Side-effects Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/side-effects.mdx Optional side-effects that run when the machine starts or stops. They receive the current state and a send function to dispatch events. A cleanup function can be returned to free resources. ```typescript const machine = defineMachine({ onStart: ({ state, send }) => { /* ... */ }, onStop: ({ state }) => { /* ... */ }, // ... }); ``` -------------------------------- ### Preview Production Build Source: https://github.com/maurice/yay-machine/blob/main/packages/site/README.md Previews the production build locally before deploying. Run from the project root. ```bash npm run -w @yay-machine/site preview ``` -------------------------------- ### Build Production Site Source: https://github.com/maurice/yay-machine/blob/main/packages/site/README.md Builds the production-ready static assets for the documentation site into the `./dist/` directory. ```bash npm run site:build ``` -------------------------------- ### Login Machine Usage (TypeScript) Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/login/index.mdx Demonstrates how to use the login machine, including creating an instance, sending events, and subscribing to state changes. ```typescript import { loginMachine } from "@yay-machine/example-machines/src/login/loginMachine"; // Create a new instance of the login machine const login = loginMachine.createInstance(); // Subscribe to state changes login.subscribe((state) => { console.log("Current state:", state); }); // Send events to the machine login.send({ type: "LOGIN", payload: { username: "test", password: "test" } }); // After 1 second, the machine will automatically send a LOGOUT event // and transition back to the 'idle' state. ``` -------------------------------- ### State Transition Configuration Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/transitions.mdx Illustrates the basic configuration for defining state transitions within a machine. This shows how to map an event in a specific state to a target state. ```typescript const machine = defineMachine({ states: { [stateName]: { on: { [EVENT_NAME]: { to: "nextStateName", /* ... other options ... */ }, // ... }, }, // ... }, }); ``` -------------------------------- ### Transport Definition Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/transitions.mdx Defines a Transport class used within the connection machine example. This class likely handles the actual network communication logic. ```typescript import { EventObject } from "@yay-machine/core"; export class Transport { private _isConnected = false; private _connectionAttempts = 0; private _maxConnectionAttempts = 3; constructor(private _url: string) {} async connect(): Promise { this._connectionAttempts++; console.log(`Connecting to ${_url}... (Attempt ${_connectionAttempts}/${_maxConnectionAttempts})`); // Simulate connection logic await new Promise((resolve) => setTimeout(resolve, 500)); if (Math.random() > 0.2) { this._isConnected = true; console.log("Connected!"); } else { console.error("Connection failed."); if (this._connectionAttempts >= this._maxConnectionAttempts) { throw new Error("Max connection attempts reached"); } throw new Error("Connection error"); } } disconnect(): void { this._isConnected = false; console.log("Disconnected."); } send(data: any): void { if (!this._isConnected) { throw new Error("Not connected"); } console.log("Sending data:", data); // Simulate sending data } get isConnected(): boolean { return this._isConnected; } } ``` -------------------------------- ### createMachine(config) Source: https://context7.com/maurice/yay-machine/llms.txt A convenience wrapper around `defineMachine(...).newInstance()`. Use this when you only need one instance and do not need to reuse the machine definition. ```APIDOC ## `createMachine(config)` — Create a single-use machine instance A convenience wrapper around `defineMachine(...).newInstance()`. Use this when you only need one instance and don't need to reuse the machine definition. ```typescript import { createMachine } from "yay-machine"; const counter = createMachine< { readonly name: "counting"; readonly value: number }, { readonly type: "INCREMENT" } | { readonly type: "RESET" } >({ initialState: { name: "counting", value: 0 }, states: { counting: { on: { INCREMENT: { to: "counting", data: ({ state }) => ({ value: state.value + 1 }), }, RESET: { to: "counting", data: () => ({ value: 0 }), }, }, }, }, }); counter.start(); counter.send({ type: "INCREMENT" }); counter.send({ type: "INCREMENT" }); counter.send({ type: "INCREMENT" }); console.log(counter.state.value); // 3 counter.send({ type: "RESET" }); console.log(counter.state.value); // 0 ``` ``` -------------------------------- ### Connection Machine Definition Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/transitions.mdx Defines the states and transitions for a connection machine, including various error and disconnection scenarios. This serves as a comprehensive example for transition types. ```typescript import { MachineDefinition } from "@yay-machine/core"; export type State = "disconnected" | "connecting" | "connected" | "reattemptConnection" | "connectionError"; export type Event = "CONNECT" | "CONNECTED" | "ERROR" | "DISCONNECTED"; export const connectionMachine: MachineDefinition = { initial: "disconnected", states: { disconnected: { on: { CONNECT: "connecting", }, }, connecting: { on: { CONNECTED: "connected", ERROR: { // Example of conditional transitions target: "connectionError", cond: (event) => event.payload?.reason === "invalid auth", }, // Default error transition ERROR: "reattemptConnection", DISCONNECTED: "disconnected", }, }, connected: { on: { ERROR: "reattemptConnection", SEND: "connected", // Stay in the same state DISCONNECTED: "disconnected", }, }, reattemptConnection: { on: { // Example of delayed transitions CONNECT: { target: "connecting", after: 1000, // Reattempt after 1 second }, // Example of guarded transitions ERROR: { target: "connectionError", cond: (event) => event.payload?.maxAttemptsReached, }, DISCONNECTED: "disconnected", }, }, connectionError: { type: "final", // This is a final state }, }, }; ``` -------------------------------- ### Define a state machine at compile-time with TypeScript Source: https://github.com/maurice/yay-machine/blob/main/README.md Define the states, events, and transitions for a state machine using the `defineMachine` function. This example demonstrates a number guessing game. ```typescript // guessMachine.ts import { defineMachine } from "yay-machine"; interface GuessState { readonly name: | "pickNumber" | "playing" | "guessedCorrectly" | "tooManyIncorrectGuesses"; readonly answer: number; readonly numGuesses: number; readonly maxGuesses: number; } interface GuessEvent { readonly type: "GUESS"; readonly guess: number; } interface NewGameEvent { readonly type: "NEW_GAME"; } const incrementNumGuesses = ({ state, }: { readonly state: GuessState; }): GuessState => ({ ...state, numGuesses: state.numGuesses + 1, }); /** * Guess the number from 1 to 10 */ export const guessMachine = defineMachine< GuessState, GuessEvent | NewGameEvent >({ initialState: { name: "pickNumber", answer: 0, numGuesses: 0, maxGuesses: 5 }, states: { pickNumber: { always: { to: "playing", data: ({ state }) => ({ ...state, answer: Math.ceil(Math.random() * 10), numGuesses: 0, }), }, }, playing: { on: { GUESS: [ { to: "guessedCorrectly", when: ({ state, event }) => state.answer === event.guess, data: incrementNumGuesses, }, { to: "tooManyIncorrectGuesses", when: ({ state }) => state.numGuesses + 1 === state.maxGuesses, data: incrementNumGuesses, }, { to: "playing", data: incrementNumGuesses, }, ], }, }, guessedCorrectly: { on: { NEW_GAME: { to: "pickNumber", data: ({ state }) => state }, }, }, tooManyIncorrectGuesses: { on: { NEW_GAME: { to: "pickNumber", data: ({ state }) => state }, }, }, }, }); ``` -------------------------------- ### machine.newInstance(config?) Source: https://context7.com/maurice/yay-machine/llms.txt Called on a `MachineDefinition` returned by `defineMachine()`. Accepts an optional `instanceConfig` to override the default `initialState` for that specific instance. Multiple independent instances may be created from a single definition. ```APIDOC ## `machine.newInstance(config?)` — Create an independent machine instance Called on a `MachineDefinition` returned by `defineMachine()`. Accepts an optional `instanceConfig` to override the default `initialState` for that specific instance. Multiple independent instances may be created from a single definition. ```typescript import { defineMachine } from "yay-machine"; const guessMachine = defineMachine< { readonly name: "playing" | "won" | "lost"; readonly answer: number; readonly attempts: number }, { readonly type: "GUESS"; readonly value: number } >({ initialState: { name: "playing", answer: 7, attempts: 0 }, states: { playing: { on: { GUESS: [ { to: "won", when: ({ state, event }) => event.value === state.answer, data: ({ state }) => ({ ...state, attempts: state.attempts + 1 }), }, { to: "lost", when: ({ state }) => state.attempts + 1 >= 3, data: ({ state }) => ({ ...state, attempts: state.attempts + 1 }), }, { to: "playing", data: ({ state }) => ({ ...state, attempts: state.attempts + 1 }), }, ], }, }, }, }); // Two independent instances with different initial answers const game1 = guessMachine.newInstance({ initialState: { name: "playing", answer: 3, attempts: 0 } }).start(); const game2 = guessMachine.newInstance({ initialState: { name: "playing", answer: 9, attempts: 0 } }).start(); game1.send({ type: "GUESS", value: 3 }); console.log(game1.state.name); // "won" console.log(game2.state.name); // "playing" (independent) ``` ``` -------------------------------- ### Get Machine's Current State Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/state.mdx Accesses the current state of a running machine instance. The `state` property is a getter that returns the machine's current state object. ```typescript const connection = connectionMachine.newInstance().start(); assert(connection.state).deepStrictEqual({ name: "disconnected" }); ``` -------------------------------- ### Using the FullScreen Component Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/experiments/full-screen.mdx Import and use the FullScreen component to wrap content that should be toggled into full screen. The `client:load` directive ensures the component is hydrated on the client. ```javascript import { FullScreen } from "../../../components";
Hello world
``` -------------------------------- ### Toggle Machine Usage (TypeScript) Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/toggle/index.mdx Illustrates how to use the toggle machine in an application, including sending events and subscribing to state changes. ```typescript import { createActor } from "@yay-machine/core"; import { toggleMachine } from "./toggleMachine"; const toggleActor = createActor(toggleMachine); toggleActor.start(); console.log(toggleActor.getSnapshot().value); // "off" toggleActor.send({ type: "TOGGLE" }); console.log(toggleActor.getSnapshot().value); // "on" // Persisting state toggleActor.subscribe((snapshot) => { localStorage.setItem("toggleState", JSON.stringify(snapshot.value)); }); // Resuming state const savedState = localStorage.getItem("toggleState"); if (savedState) { toggleActor.start(JSON.parse(savedState)); } ``` -------------------------------- ### Elevator Controller Machine Usage Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/elevators-controller/index.mdx Demonstrates how to use the elevator controller machine. It initializes the controller with elevator instances and sends requests. ```typescript import { createActor } from "@yay-machine/core"; import { elevatorMachine } from "@yay-machine/example-machines/src/elevator"; import { elevatorControllerMachine } from "./elevatorControllerMachine"; // Create individual elevator actors const elevator1 = createActor(elevatorMachine, { id: "elevator1", context: { currentFloor: "1", destination: null, state: "idle", }, }); const elevator2 = createActor(elevatorMachine, { id: "elevator2", context: { currentFloor: "1", destination: null, state: "idle", }, }); // Initialize the controller machine with elevator actors const controller = createActor(elevatorControllerMachine, { context: { elevators: { elevator1: elevator1.getSnapshot(), elevator2: elevator2.getSnapshot(), }, pendingRequests: [], }, }); // Subscribe to elevator state changes to handle arrivals elevator1.subscribe((snapshot) => { if (snapshot.matches("doorsOpen") && snapshot.context.requestedByController) { controller.send("ELEVATOR_ARRIVED", { floor: snapshot.context.currentFloor }); } }); elevator2.subscribe((snapshot) => { if (snapshot.matches("doorsOpen") && snapshot.context.requestedByController) { controller.send("ELEVATOR_ARRIVED", { floor: snapshot.context.currentFloor }); } }); // Start the controller machine controller.start(); // Send a request for floor 5 controller.send("REQUEST_FLOOR", { floor: "5" }); // Send another request for floor 10 controller.send("REQUEST_FLOOR", { floor: "10" }); // You can inspect the controller's state console.log(controller.getSnapshot().context); // To update the controller's context with the latest elevator states: // controller.send({ type: "UPDATE_ELEVATOR_STATE", payload: { elevatorId: "elevator1", state: elevator1.getSnapshot() } }); ``` -------------------------------- ### Event Payload for Transitions and State Data Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/reference/events.mdx Utilize event payload data to determine conditional transitions and generate state data for the next state. This example shows how the 'url' from a 'CONNECT' event is used. ```typescript type ConnectionState = | { readonly name: "disconnected" } | { readonly name: "connecting" | "connected"; readonly url: string } | { readonly name: "connectionError"; readonly errorMessage: string }; const connectionMachine = defineMachine({ initialState: { name: "disconnected", url: "" }, states: { disconnected: { on: { CONNECT: [ { to: "connectionError", when: ({ event }) => isInvalidUrl(event.url), data: ({ event }) => ({ errorMessage: `Bad url: ${event.url}` }), }, { to: "connecting", data: ({ event }) => ({ url: event.url }), }, ], }, }, connecting: { on: { ERROR: { to: "connectionError", data: ({ event }) => ({ errorMessage: String(event.error) }), }, }, }, // ... }, }); const connection = connectionMachine.newInstance().start(); connection.subscribe(({ state }) => { if (state.name === "connecting") { console.log("connecting to", state.url); } if (state.name === "connectionError") { console.log("connectionFailed: %s", state.errorMessage); } }); connection.send({ type: "CONNECT", url: "ws://localhost:9999/api" }); ``` -------------------------------- ### Handle Events in Any State with Top-Level `on` Source: https://context7.com/maurice/yay-machine/llms.txt Use top-level `on` in the machine definition to handle events regardless of the current state. State-specific transitions take precedence. This example demonstrates handling a `CANCEL` event from any non-terminal state. ```typescript import { defineMachine } from "yay-machine"; type OrderState = | { readonly name: "processing" } | { readonly name: "shipped" } | { readonly name: "delivered" } | { readonly name: "cancelled"; readonly reason: string }; type OrderEvent = | { readonly type: "SHIP" } | { readonly type: "DELIVER" } | { readonly type: "CANCEL"; readonly reason: string } | { readonly type: "HEARTBEAT" }; const orderMachine = defineMachine({ initialState: { name: "processing" }, states: { processing: { on: { SHIP: { to: "shipped" } } }, shipped: { on: { DELIVER: { to: "delivered" } } }, }, // CANCEL is handled from any non-terminal state via any-state handler on: { CANCEL: { to: "cancelled", when: ({ state }) => state.name !== "cancelled" && state.name !== "delivered", data: ({ event }) => ({ reason: event.reason }), onTransition: ({ event }) => { console.log("Order cancelled:", event.reason); }, }, HEARTBEAT: { // No `to` — stays in current state; useful for updating data without changing state data: ({ state }) => ({ ...state }), }, }, }); const order = orderMachine.newInstance().start(); order.send({ type: "SHIP" }); console.log(order.state.name); // "shipped" order.send({ type: "CANCEL", reason: "customer request" }); // logs: Order cancelled: customer request console.log(order.state); // { name: "cancelled", reason: "customer request" } ``` -------------------------------- ### Guess Machine State Definition Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/guess/index.mdx Defines the state machine for the guess the number game. Includes states for initial setup, guessing, winning, and losing, along with transitions based on user input and game logic. ```typescript import { createMachine, assign } from "@yay-machine/core"; interface State { guessesLeft: number; randomNumber: number; lastGuess?: number; } type Event = | { type: "RESTART" } | { type: "GUESS", payload: number }; export const guessMachine = createMachine({ initial: "playing", states: { playing: { on: { GUESS: [ // Win condition { guard: ({ event, context }) => event.payload === context.randomNumber, target: "won", actions: assign({ lastGuess: ({ event }) => event.payload, }), }, // Lose condition { guard: ({ context }) => context.guessesLeft - 1 === 0, target: "lost", actions: assign({ lastGuess: ({ event }) => event.payload, guessesLeft: ({ context }) => context.guessesLeft - 1, }), }, // Continue playing { actions: assign({ lastGuess: ({ event }) => event.payload, guessesLeft: ({ context }) => context.guessesLeft - 1, }), }, ], RESTART: { target: "playing", actions: assign(context => ({ guessesLeft: 5, randomNumber: Math.floor(Math.random() * 10) + 1, lastGuess: undefined, })), } }, }, won: { type: "final", }, lost: { type: "final", }, }, }); ``` -------------------------------- ### Development Scripts Source: https://github.com/maurice/yay-machine/blob/main/development.md Scripts for type-checking, linting, running tests (once or in watch mode), and building the project. ```sh npm run check # type-check, lint npm run test # run tests once npm run test:watch # run tests in watch-mode npm run build # transpile TypeScript to dist/*.js, generate .d.ts files ``` -------------------------------- ### Implement Side-Effects with `onStart`, `onStop`, `onEnter`, `onExit`, `onTransition` Source: https://context7.com/maurice/yay-machine/llms.txt Attach side-effect callbacks at machine, state, or transition levels. These callbacks can perform actions and optionally return cleanup functions. `onEnter` and `onStart` cleanups run when the state is exited or the machine stops, respectively. Transition and exit side-effects clean up immediately. ```typescript import { defineMachine } from "yay-machine"; type SocketState = | { readonly name: "disconnected" } | { readonly name: "connected"; readonly url: string }; type SocketEvent = | { readonly type: "CONNECT"; readonly url: string } | { readonly type: "DISCONNECT" } | { readonly type: "PING" }; const socketMachine = defineMachine({ initialState: { name: "disconnected" }, onStart: ({ state, send }) => { console.log("machine started"); return () => console.log("machine cleanup"); }, onStop: ({ state }) => { console.log("machine stopped in state:", state.name); }, states: { disconnected: { on: { CONNECT: { to: "connected", data: ({ event }) => ({ url: event.url }), onTransition: ({ event, send }) => { console.log("connecting to", event.url); // cleanup runs immediately after this transition return () => console.log("onTransition cleanup"); }, }, }, }, connected: { onEnter: ({ state, send }) => { console.log("entered connected state, url:", state.url); const heartbeat = setInterval(() => send({ type: "PING" }), 5000); // cleanup runs when leaving "connected" state return () => { clearInterval(heartbeat); console.log("heartbeat stopped"); }; }, onExit: ({ state }) => { console.log("leaving connected state"); }, on: { PING: { to: "connected", reenter: false }, // stay in state, don't re-run onEnter DISCONNECT: { to: "disconnected" }, }, }, }, }); const socket = socketMachine.newInstance().start(); // logs: "machine started" socket.send({ type: "CONNECT", url: "ws://example.com" }); // logs: "connecting to ws://example.com" // logs: "onTransition cleanup" // logs: "entered connected state, url: ws://example.com" socket.send({ type: "DISCONNECT" }); // logs: "heartbeat stopped" // logs: "leaving connected state" socket.stop(); // logs: "machine cleanup" // logs: "machine stopped in state: disconnected" ``` -------------------------------- ### Hardware Interface Mock Source: https://github.com/maurice/yay-machine/blob/main/packages/site/src/content/docs/examples/tape/index.mdx A mock implementation of the `HardwareInterface` used by the tape machine. It simulates hardware events and provides methods for controlling the tape head and motor. ```typescript import { EventEmitter } from "events"; export enum TapeHeadPosition { STOP = "stop", PLAY = "play", REWIND = "rewind", FAST_FORWARD = "fastForward", } export enum TapeMotorState { STOP = "stop", FORWARD = "forward", REWIND = "rewind", FAST_FORWARD = "fastForward", } export class HardwareInterface extends EventEmitter { private tapeHeadPosition: TapeHeadPosition = TapeHeadPosition.STOP; private motorState: TapeMotorState = TapeMotorState.STOP; setTapeHeadPosition(position: TapeHeadPosition) { this.tapeHeadPosition = position; console.log(`Tape head set to: ${position}`); } setMotorState(state: TapeMotorState) { this.motorState = state; console.log(`Motor set to: ${state}`); } // Simulate external events simulateStart() { this.emit("start"); } simulateEnd() { this.emit("end"); } } ```