https://github.com/bbsimonbb/octopus-turbo Skip to content Toggle navigation Sign in * Product + Actions Automate any workflow + Packages Host and manage packages + Security Find and fix vulnerabilities + Codespaces Instant dev environments + Copilot Write better code with AI + Code review Manage code changes + Issues Plan and track work + Discussions Collaborate outside of code Explore + All features + Documentation + GitHub Skills + Blog * Solutions For + Enterprise + Teams + Startups + Education By Solution + CI/CD & Automation + DevOps + DevSecOps Resources + Learning Pathways + White papers, Ebooks, Webinars + Customer Stories + Partners * Open Source + GitHub Sponsors Fund open source developers + The ReadME Project GitHub community articles Repositories + Topics + Trending + Collections * Pricing Search or jump to... Search code, repositories, users, issues, pull requests... Search [ ] Clear Search syntax tips Provide feedback We read every piece of feedback, and take your input very seriously. [ ] [ ] Include my email address so I can be contacted Cancel Submit feedback Saved searches Use saved searches to filter your results more quickly Name [ ] Query [ ] To see all available qualifiers, see our documentation. Cancel Create saved search Sign in Sign up You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session. Dismiss alert {{ message }} bbsimonbb / octopus-turbo Public * Notifications * Fork 0 * Star 31 Turbo repo with octopus-state-graph and samples 31 stars 0 forks Activity Star Notifications * Code * Issues 0 * Pull requests 0 * Discussions * Actions * Projects 0 * Security * Insights Additional navigation options * Code * Issues * Pull requests * Discussions * Actions * Projects * Security * Insights bbsimonbb/octopus-turbo This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository. main Switch branches/tags [ ] Branches Tags Could not load branches Nothing to show {{ refName }} default View all branches Could not load tags Nothing to show {{ refName }} default View all tags Name already in use A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch? Cancel Create 3 branches 1 tag Code * Local * Codespaces * Clone HTTPS GitHub CLI [https://github.com/b] Use Git or checkout with SVN using the web URL. [gh repo clone bbsimo] Work fast with our official CLI. Learn more about the CLI. * Open with GitHub Desktop * Download ZIP Sign In Required Please sign in to use Codespaces. Launching GitHub Desktop If nothing happens, download GitHub Desktop and try again. Launching GitHub Desktop If nothing happens, download GitHub Desktop and try again. Launching Xcode If nothing happens, download Xcode and try again. Launching Visual Studio Code Your codespace will open once ready. There was a problem preparing your codespace, please try again. Latest commit @bbsimonbb bbsimonbb readme ... c01f90b Dec 14, 2023 readme c01f90b Git stats * 80 commits Files Permalink Failed to load latest commit information. Type Name Latest commit message Commit time .github/workflows publish after build 2 November 16, 2023 00:50 apps rename December 13, 2023 13:42 images devtools in turborepo! November 13, 2023 19:21 packages/octopus-state-graph IStateful generic December 13, 2023 23:49 .gitignore initial commit October 3, 2023 20:38 README.md readme December 14, 2023 19:43 package.json installation instructions November 14, 2023 14:53 pnpm-lock.yaml devtools in turborepo! November 13, 2023 19:21 pnpm-workspace.yaml initial commit October 3, 2023 20:38 turbo.json nearly working October 4, 2023 14:52 View code Octopus State Graph Modelling your UI as a directed acyclic graph. Get started What am I looking at here? Simplest possible example Integration with front-end frameworks devtools README.md Octopus State Graph Modelling your UI as a directed acyclic graph. This turborepo contains: * octopus-state-graph npm package for your apps * two sample applications in React and Vue * octopus devtools extensions, available as a popup or to package and install as a Chrome extension. Get started git clone https://github.com/bbsimonbb/octopus-turbo.git cd octopus-turbo npm install -g pnpm pnpm install pnpm run dev [pizza-ui] What am I looking at here? Directed acyclic graphs are muched discussed in comp-sci, but octopus appears to be the first reusable, turnkey, ready-to-wear, off-the-shelf implementation of a DAG for application development, in any language, that I'm aware of. This is remarkable because DAGs hit a sweet spot in the middle of the three common programming paradigms (OO, event-driven, functional). Let's have a DAG as the top-level structure of our applications. Data-fetching and onChange handlers live in DAG nodes, next to the data they act on. The UI flows out from the DAG with fine-grained reactivity. Our app state is effortlessly consistent, because any outside change (user action, api result) unleashes a graph traversal. Our UI components become much simpler, because they just need to dumbly reflect values in the graph. In the apps folder, there are two versions of the same sample UI, one with React, one with Vue. Click around. Simple on the surface, there are a lot of rules to implement... * Total price sums pizza, delivery and tip. The value of tip may depend on the value of pizza * Any of the five user controls can invalidate the order. * The state of the (orange) pizza control depends on user input, the size chosen and the base chosen. * Warnings only show if you've interacted with a control, or if you've tried to submit. * Warnings only show if preconditions are met. Why bully you to choose a pizza when you haven't chosen a base yet? Like many (all?) UI's, this UI is a directed acyclic graph. It could be modelled as an object-oriented system, or an event-driven one, or with a functional paradigm, but it intrinsically is a directed acyclic graph, because this is the loosest possible structure that doesn't admit infinite loops. The order of controls on screen is usually a good approximation of the structure of the graph, but of course in a UI the user can interact with controls in any order. How are we going to ensure that the state is always consistent? Let's see what happens when we make the graph structure explicit... [devtools2] This graph, generated by octopus-state-graph, visualized with octopus devtools, shows you the structure and state of your running application. Essentially, each user control on screen is underpinned by a node in the graph. A node has a name, a value (val), methods and a recalculate function (called reup() because it's shorter and I'm a huge Wire fan.) reup() can reference any other node by name in its argument list. This is all we need to build the graph. The methods property contains all the methods that will modify val. (This is your responsibility. Only a node's own reup() and methods should modify its val.) Octopus' role is simple: Firstly, on build(), it builds the graph based on the call signatures of all the reup() functions, checking that the requested dependencies exist, and that no cycles are created. Secondly, octopus ensures that when a node's val changes (when a method returns), the graph will be traversed, and all the reup() functions starting with that of the changed node, will be called sequentially. (The sequence is determined by the topological sort of the graph.) As such, for any given external change, a node only recalculates if it is downstream of the change, and it only reacalculates once, after it's predecessors have updated. (There is a ton of scope for optimising traversals. Methods could report if they made a change, or we could detect this. Nodes would then be reupped only if necessary. Going further, a tricky implementation with promises could let different branches of a traversal proceed independently, such that a slow node only delayed downstream branches that depend on it. Stay tuned!) Nodes can fetch data. In many situations, it will make much more sense to fetch your data into this persistent, reactive structure, rather than fetching from your UI components that come and go as the user navigates. Unlike a reducer, a graph traversal will happily wait while a network call completes, and downstream nodes will then recalculate taking account of the fresh context. Alternatively, a node can launch a network request and return immediately. Antecedent nodes might go into a waiting state, which you can use to control spinners etc. Then when the fetch returns, a node method handles the return and a new traversal is initiated, clearing the spinners and displaying the fresh data and any cascading effects. So now the picture emerges. Your nodes lie at the intersection of your system and the outside world. Like in OO, they encapsulate a bit of state, and the methods that modify it. Like event-driven systems, they react to upstream changes and their responsibility ends when they publish their value. There's a hat-tip to functional programming and one-way data flow in the notion of a traversal, but this approach is intentionally much less dogmatic. State, both private and published, can accumulate in nodes, and reup() functions can be async and impure. And we get to mutualise, in the reup() function, the magic sauce that combines some user input with the current state of the system to produce the node's current value, a value to which downstream nodes can then react. All this is done with no funny business. Your val is not proxied, there's no expensive change detection and very limited passing around of functions. There are no new concepts. Node code is biblically simple. A super possibility of octopus is reporting nodes. A reporting node chooses its predecessors not by name, but with a filter function. Look at the totalPrice and allValid nodes in the sample applications to see how this is done. Any new node whose published val contains a price will automatically contribute to the total. The last thing to cover is serialisation. In complex applications, you may want to be able to save and reload your graphs. To do this, we should distinguish between hot and cold state. Hot state is the full picture, presented to the UI layer. In this pizza example, the full list of option values (present in the hot state) comes from the source code, so there's no need to duplicate it into the cold state. The cold state will just need to include the user's choices among the options. The total price is trivially calculated so again there's no need to save it. So to save our graph, we just need to implement saveState() and loadState() on our options. When we call graph.saveState(), we get a little object containing just the saved user input, together with two properties that allow us to efficiently rehydrate the graph without needing to rebuild. Simplest possible example See the tests for working simple examples. Essentially you need to... const graph = createGraph() graph.addNode("nodeName", aNode) ... if(/* you're starting afresh */) graph.build() else graph.loadState( JSON.parse(savedState) ) // your UI is live, then when you're finished... const savedState = JSON.stringify(graph.saveState()) Integration with front-end frameworks To integrate with a front-end framework, we just need the framework to observe val. In Vue, this is easily accomplished by wrapping val in reactive(). For react we need to grab mobx, and wrap val in observable(), and our react components with observer(). Additionally, mobx likes you to tag state-modifying functions as actions, so that all actions can complete before the DOM is modified. You should do this at the outermost level, wrapping your handlers with action() in your react components. You should also wrap any callbacks you create inside reup() and methods. All this is demonstrated in the React sample. devtools DO NOT MISS the devtools extension (screenshot above). You can visualise the graph of your UI, see in real time what nodes you're interacting with, what value they publish, and navigate directly to the source. Click the octopus to bring up devtools in a popup. It's not in the Chrome store yet, so to install it you'll need to download and build the project, then Extensions => Pack extension [octopus-ph] About Turbo repo with octopus-state-graph and samples Resources Readme Activity Stars 31 stars Watchers 1 watching Forks 0 forks Report repository Releases 1 Initial release Latest Nov 12, 2023 Packages 0 No packages published Languages * TypeScript 66.3% * Vue 22.5% * CSS 5.7% * JavaScript 4.3% * HTML 1.2% Footer (c) 2023 GitHub, Inc. Footer navigation * Terms * Privacy * Security * Status * Docs * Contact * You can't perform that action at this time.