{"id":37179636,"url":"https://github.com/dotchain/fuss","last_synced_at":"2026-01-14T20:54:12.215Z","repository":{"id":57554044,"uuid":"173857307","full_name":"dotchain/fuss","owner":"dotchain","description":"Functional strongly-typed streams based reactive framework and tools","archived":false,"fork":false,"pushed_at":"2019-04-17T18:18:44.000Z","size":1597,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-06-20T15:52:31.704Z","etag":null,"topics":["frp","functional","functional-reactive-programming","react","reactive","reactive-programming","streams","ux"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dotchain.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-03-05T02:24:57.000Z","updated_at":"2019-04-17T18:18:42.000Z","dependencies_parsed_at":"2022-09-26T19:32:32.750Z","dependency_job_id":null,"html_url":"https://github.com/dotchain/fuss","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/dotchain/fuss","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotchain%2Ffuss","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotchain%2Ffuss/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotchain%2Ffuss/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotchain%2Ffuss/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dotchain","download_url":"https://codeload.github.com/dotchain/fuss/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dotchain%2Ffuss/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28434500,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T18:57:19.464Z","status":"ssl_error","status_checked_at":"2026-01-14T18:52:48.501Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["frp","functional","functional-reactive-programming","react","reactive","reactive-programming","streams","ux"],"created_at":"2026-01-14T20:54:11.368Z","updated_at":"2026-01-14T20:54:12.206Z","avatar_url":"https://github.com/dotchain.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Fuss\n\n[![Status](https://travis-ci.com/dotchain/fuss.svg?branch=master)](https://travis-ci.com/dotchain/fuss?branch=master)\n[![GoDoc](https://godoc.org/github.com/dotchain/fuss?status.svg)](https://godoc.org/github.com/dotchain/fuss)\n[![codecov](https://codecov.io/gh/dotchain/fuss/branch/master/graph/badge.svg)](https://codecov.io/gh/dotchain/fuss)\n[![Go Report Card](https://goreportcard.com/badge/github.com/dotchain/fuss)](https://goreportcard.com/report/github.com/dotchain/fuss)\n\nFUSS is an experimental functional/reactive framework using strongly-typed streams.\n\n## Goals\n\nThe main goal is to build a reactive  UX framework which:\n\n1. Composes nicely: pure functions are the typical case\n2. Uses strong static typing: compiler errors with wrong usage\n3. Uses [streams](https://godoc.org/github.com/dotchain/dot/streams) as the basis for time-varying data\n4. Very little magic in the framework\n5. Framework can co-exist with non-reactive or other implementations of reactive code.\n\n## Status\n\nThe project is experimental and all interfaces may change\n\n## Contents\n1. [Goals](#goals)\n2. [Status](#status)\n3. [TODO MVC Example](#todo-mvc-example)\n    1. [Todo and TodoList](#todo-and-todolist)\n    2. [Generating the streams types](#generating-the-streams-types)\n    3. [The Todo View](#the-todo-view)\n    4. [Input](#input)\n    5. [The filtered list](#the-filtered-list)\n    6. [Change handling and stateful components](#change-handling-and-stateful-components)\n    7. [Generating the factory code](#generating-the-factory-code)\n    8. [Collaborative demo](#collaborative-demo)\n4. [Limitations](#limitations)\n\n## TODO MVC Example\n\nThe demo [TODO MVC\napp](https://dotchain.github.io/fuss/todo/html/index.html) is built\nout of the [todo](https://github.com/dotchain/fuss/tree/master/todo) folder.\n\n### Todo and TodoList\n\nThe core data type for the todo app is a simple struct to hold the\ntodo item and a slice to represent a collection of these:\n\n```golang todo.global\n\n// Todo represents an item in the TODO list.\ntype Todo struct {\n\tID          string\n\tComplete    bool\n\tDescription string\n}\n\n// TodoList represents a collection of todos\ntype TodoList []Todo\n\n```\n\n### Generating the streams types\n\nThe code generation tool\n[dotc](https://godoc.org/github.com/dotchain/dot/x/dotc) can be used\nto augment the types here with additional methods.  In particular,\nmuch of the UI will use **streams** of these values.\n\n```golang codegen.global\nfunc generateTypes() {\n\t_, self, _, _ := runtime.Caller(0)\n\toutput := filepath.Join(filepath.Dir(self), \"generated1.go\")\n\n\tinfo := dotc.Info{\n\t\tPackage: \"todo\",\n\t\tStructs: []dotc.Struct{{\n\t\t\tRecv: \"t\",\n\t\t\tType: \"Todo\",\n\t\t\tFields: []dotc.Field{{\n\t\t\t\tName: \"Complete\",\n\t\t\t\tKey:  \"complete\",\n\t\t\t\tType: \"bool\",\n\t\t\t}, {\n\t\t\t\tName: \"Description\",\n\t\t\t\tKey:  \"desc\",\n\t\t\t\tType: \"string\",\n\t\t\t}},\n\t\t}},\n\t\tSlices: []dotc.Slice{{\n\t\t\tRecv:     \"t\",\n\t\t\tType:     \"TodoList\",\n\t\t\tElemType: \"Todo\",\n\t\t}},\n\t}\n\tcode, err := info.Generate()\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\terr = ioutil.WriteFile(output, []byte(code), 0644)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n```\n\nSee\n[Codegen](https://github.com/dotchain/fuss/blob/master/todo/codegen.go)\nwhere this is used.\n\nThe `TodoStream` type exposes the current value via the `Value` field\nand supports an `Update()` method to update the value whole-sale. Such\nupdates do not change the current value but can be thought of as\nappending to the sequence of values a particular `Todo` has. The\nlatest value can be obtained via `TodoStream:Latest()` and callers can\nregister for notifications on it.\n\nMore interestingly, the code snippet above will generate methods on\nthe stream to fetch sub-streams for, say, the `Complete` field. This\nsub-stream holds a simple `bool` value and when it is updated, the\ncorresponding `todo` itself is updated with that field changed. The\nstream also does \"merging\" --  if the `Complete` and `Description`\nfields were modified indepdently, the latest of the `todo` will be\nmerge of the changes.\n\nThe `TodoListStream` similarly exposes a `TodoStream` for each element\nin the array and edits to the individual elements correectly get\npropagated to the parent.\n\n### The Todo View\n\nThe following snippet renders a single todo item:\n\n```golang todo.global\n// Todo renders a Todo item\nfunc todo(deps *todoDeps, todoStream *TodoStream) dom.Element {\n\treturn deps.run(\n\t\t\"root\",\n\t\tdom.Styles{},\n\t\tdeps.checkboxEdit(\"cb\", dom.Styles{}, todoStream.Complete(), \"\"),\n\t\tdeps.textEdit(\"textedit\", dom.Styles{}, todoStream.Description()),\n\t)\n}\n\ntype TodoFunc = func(key interface{}, todoStream *TodoStream) dom.Element\ntype todoDeps struct {\n\trun          dom.RunFunc\n\tcheckboxEdit dom.CheckboxEditFunc\n\ttextEdit     dom.TextEditFunc\n}\n```\n\nEach component has three parts: the core function (`todo(..)`), the\nset of dependencies used  by this function (`todoDeps`) and a\nsignature for how callers can use this function `TodoFunc`.\n\nA few notes:\n1. The first argument of the function must be a pointer to the\ndependency struct\n2. The dependency struct should hold all the other components this one\nintends to use.  In particular, the function signatures are named\nhere.\n3. The signature replaces the first arg with a generic \"key\" (which\nwill be explained shortly).\n\nThe main function itself creates sub-components as needed using  the\ndependencies struct. The code-generation framework produces a single\nartifact for each compnent -- a factory function that creates\ninstances of the component:\n\n```golang skip\nfunc NewTodo() (update TodoFunc, close func()) {\n    ...\n}\n```\n\nThe idea is that any consumer of the `todo` component would use this\nfunction above to create an instance.  The generated function\nimplements the logic for creating the dependency functions. In\nparticular, the dependency functions take a \"key\" as the first\nparameter (with the rest matching the corresponding component). The\ngenerated scaffolding checks if the key was used before and if so\nreuses the last instance (calling on its update method). If the key is\nnew, it calls the factory method of the sub component.\n\nThe generated factory function also does simple memoization: if the\nargs to the update method are same as before, it results the results\nfrom the last round.\n\n### Input\n\nGoing back to the example above, the checkbox was provided with a\n`streams.Bool` which controls both whether it shows as checked or not\nas well as the output from the checkbox.  When the user toggles the\ncheckbox, the checkbox component updates the provided input  stream by\ncalling `streams.Bool:Update(newValue)` on it.\n\nThe `todo` view does not directly handle this but since the checkbox\nstream was created via `todoStream.Complete()` any changes to the\nboolean stream end up modifying the corresponding `todoStream`\n\nThese changes to the stream do not cause any automatic re-rendering\nthough. Instead  the are propagated up until some point where the\nstream is considered the *state* of a component.  \n\n### The filtered list\n\nThe parent of the `todo()` view is a list of todos (filtered by\nwhether they are active or not based on a filter setting)\n\n```golang todo.global\n// FilteredList renders  a list of filtered todos\n//\n// Individual tasks can be modified underneath.\nfunc filteredList(deps *filteredListDeps, filter *streams.S16, todos *TodoListStream) dom.Element {\n\treturn deps.vRun(\n\t\t\"root\",\n\t\tdom.Styles{},\n\t\ttodos.Value.renderTodo(func(index int, t Todo) dom.Element {\n\t\t\tdone := filter.Value == controls.ShowDone\n\t\t\tactive := filter.Value == controls.ShowActive\n\t\t\tif t.Complete \u0026\u0026 active || !t.Complete \u0026\u0026 done {\n\t\t\t\treturn nil\n\t\t\t}\n\n\t\t\treturn deps.todo(t.ID, todos.Item(index))\n\t\t})...,\n\t)\n}\n\ntype FilteredListFunc = func(key interface{}, filter *streams.S16, todos *TodoListStream) dom.Element\ntype filteredListDeps struct {\n\tvRun dom.VRunFunc\n\ttodo TodoFunc\n}\n```\n\nThis code creates a `VRun` flex-rows container (using `dom.VRun`) but\nalso takes a stream of TodoList as the arg. It walks through each\nelement of the TaskList value and creates a child todo component\n(assuming the filter conditions were satisfied).\n\nThe dependency struct here indicates both sub-components are\nneeded. The code also uses the task ID as the key when invoking\n`deps.todo(t.ID, ...)`.  This ensures that if an item gets shuffled\naround, it will be reused still.\n\n### Change handling and stateful components\n\nSo far there is no specific code for change handling.  The changes\nfrom checkboxes and textedits simply propagaate upwards but no\nautomatic re-rendering happens until it hits a *stateful component*. \nA stateful component takes a state parameter as well as returns it. \nThe generated factory function automatically rerenders a component\nwhen its state changes.\n\nAn example of such a component is the full app itself: the list of\ntodos are maintained as state (though in a real app, they would be\nsaved on the server but thats a different demo):\n\n```golang todo.global\n// App hosts the todo MVC app\nfunc app(deps *appDeps, state *TodoListStream) (*TodoListStream, dom.Element) {\n\tif state == nil {\n\t\t// TODO: fetch this from the network\n\t\tstate = \u0026TodoListStream{\n\t\t\tStream: streams.New(),\n\t\t\tValue: TodoList{\n\t\t\t\tTodo{\"one\", true, \"First task\"},\n\t\t\t\tTodo{\"two\", false, \"Second task\"},\n\t\t\t},\n\t\t}\n\t}\n\n\treturn state, deps.chrome(\n\t\t\"root\",\n\t\tdeps.textView(\"h\", dom.Styles{}, \"FUSS TODO\"),\n\t\tdeps.listView(\"root\", state),\n\t\tdeps.a(\n\t\t\t\"a\",\n\t\t\tdom.Styles{},\n\t\t\t\"https://github.com/dotchain/fuss\",\n\t\t\tdeps.textView(\"tv\", dom.Styles{}, \"github\"),\n\t\t),\n\t)\n}\n\ntype AppFunc = func(key interface{}) dom.Element\ntype appDeps struct {\n\ttextView dom.TextViewFunc\n\tlistView ListViewFunc\n\ta        dom.AFunc\n\tchrome   controls.ChromeFunc\n}\n```\n\nAs describe before, the distinguishing marks  of a stateful component\nare:\n\n1. It has a parameter that captures the state (these should be named\nxyzState).\n\n2. It returns the state back. At a minimum, this is needed for\ninitialization (state is initially the zero value for its type)\n\nWhen the checkbox stream is updated, that gets propagated all the way\nup to the app where the TodoListStream is the state.  So, the app\ncomponent is re-rendered and effectively this causes  the whole\nsub-tree to be re-rendered (unless a sub-component has the same args\nas before, in which case the previous results are reused).\n\n### Generating the factory code\n\nThe factory code can be fairly completely auto-generated with a simple\nstub like this:\n\n\n```golang codegen.global\nfunc generateComponents() {\n\t_, self, _, _ := runtime.Caller(0)\n\toutput := filepath.Join(filepath.Dir(self), \"generated2.go\")\n\tskip := []string{\"generated2.go\"}\n\tinfo, err := fussy.ParseDir(filepath.Dir(self), \"todo\", skip)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n\terr = ioutil.WriteFile(output, []byte(fussy.Generate(*info)), 0644)\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n```\n\nSee\n[Codegen](https://github.com/dotchain/fuss/blob/master/todo/codegen.go)\nwhere this is used.\n\n### Collaborative demo\n\nThe example in the\n[collab](https://github.com/dotchain/fuss/tree/master/todo/html)\nfolder can be used to demonstrate a **collaborative** todo list:\n\nStarting the local server:\n\n```sh\n$ cd github.com/dotchain/fuss/todo/collab\n$ go run server\n```\n\nStarting the gopherjs session:\n\n```sh\n$ gopherjs serve github.com/dotchain/fuss/todo/collab --http=:8081\n```\n\nNow open multiple browser tabs to\n[http://localhost:8081](http://localhost:8081) and see that they all\nshare the same TODO list.\n\nThe actual todo list is served from a local file (todo.bolt) and so\nthe state persists even when the browser session is restarted.\n\n## Limitations\n\nThere are a lot of limitations with this still:\n\n1. The `xyzFunc` types must all use named args (as these names are\nused within the generated code).  State args should not appear on this\nFunc type.\n\n2. The dependency function can be named anything as its type is\ndeduced from the first parameter but the `xyzFunc` declaration should\nmatch the function name.\n\n3. The actual core function (such as `todo` in the example) should\nalways be unexported.  This is also true for the dependency\nstructures.  The `xyzFunc` declaration can be exported or private and\nthe generated `NewXyz()` will mirror this.\n\n4. Memoization works by comparing using `==` with one exception:\nvariadic types are handled by checking each value instead of the\nslice.  Non-comparable types can implement `Equals(other)` methods to\nprovide custom equals methods.\n\n5. The generated code assumes it lives in the same package.  But the\nactual code generation should skip this file (the generation code\nsnippets in this README file correctly deal with this).\n\n6. The [dotc](https://godoc.org/github.com/dotchain/dot/x/dotc) code\ngenerator needs to be explicitly provided with the type info --  it is\nnot deduced from the code yet.  This is rather finicky, particularly\nif dealing with struct of structs and such as it depends on the\n[dot](https://godoc.org/github.com/dotchain/dot) infrastructure.\n\n7. The gopherjs bundle is **huge**.  None of the underlying components\nhave been optimized in any way for the bundle.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdotchain%2Ffuss","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdotchain%2Ffuss","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdotchain%2Ffuss/lists"}