{"id":13623834,"url":"https://github.com/jamesknelson/maxim","last_synced_at":"2025-04-07T22:32:12.027Z","repository":{"id":33646154,"uuid":"37298581","full_name":"jamesknelson/maxim","owner":"jamesknelson","description":"Maxim provides a simple way to structure JavaScript applications, letting you focus on the parts that matter.","archived":false,"fork":false,"pushed_at":"2015-10-10T09:40:09.000Z","size":143,"stargazers_count":45,"open_issues_count":0,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2024-12-28T16:02:57.543Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jamesknelson.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-06-12T02:52:16.000Z","updated_at":"2019-10-01T04:05:41.000Z","dependencies_parsed_at":"2022-08-17T19:05:19.340Z","dependency_job_id":null,"html_url":"https://github.com/jamesknelson/maxim","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesknelson%2Fmaxim","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesknelson%2Fmaxim/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesknelson%2Fmaxim/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamesknelson%2Fmaxim/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamesknelson","download_url":"https://codeload.github.com/jamesknelson/maxim/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247741290,"owners_count":20988366,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2024-08-01T21:01:36.172Z","updated_at":"2025-04-07T22:32:07.689Z","avatar_url":"https://github.com/jamesknelson.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Maxim.js\n\n*|ˈmaksɪm| n. a short, pithy statement expressing a general truth or rule of conduct*\n\nMaxim provides structure to your browser-based applications. It does this through a set of tools and conventions which help you fashion apps with unidirectional data flow, and without decision paralysis. It is unashamedly opinionated.\n\nMaxim is small - five files, currently totaling 143 lines. It can be this small as it leaves the heavy lifting to [RxJS](https://github.com/Reactive-Extensions/RxJS). This is good for you because:\n\n- You can grok Maxim's code in the time it takes to drink a coffee\n- Improvements in RxJS automatically make Maxim even more useful\n- Maxim won't stop being useful if I'm hit by a bus\n\n## Getting Started\n\nMaxim apps start as clones of the Maxim starter kit, currently called [react-black-triangle](https://github.com/jamesknelson/react-black-triangle). This prevents you from re-inventing build scripts, directory structure and routing, and allows you to start being productive immediately.\n\nSpeaking of being productive, it'll only take you two minutes to get started:\n\n```\ngit clone git@github.com:jamesknelson/react-black-triangle.git\ncd react-black-triangle\nnpm install\nnpm install -g gulp\nnpm start\nopen http://localhost:3000\n```\n\nThis gets you a black triangle ([why a black triangle?!](http://rampantgames.com/blog/?p=7745)), from which point you can either start adding your own features or dig into the workings of Maxim. Choose your own adventure!\n\n## How Maxim Works\n\nMaxim converts multiple streams of input events into a number of stateful models, and then interacts with the outside world and the application's inputs based on the most current values of these models. The flow looks something like this:\n\n![Maxim Data Flow](http://jamesknelson.com/maxim.png)\n\n### Structure\n\nA Maxim app is split into modules of four types: Controls, Models, Reducers and Actors.\n\nData flows through these types in a specific order; entering the application in the Control modules, moving through Models, Reducers, and finally being used to perform some action on the outside world (such as displaying a UI) in the Actors.\n\nEach of your Control, Model, Reducer and Actor files export a single function, which is run by Maxim to to set up the module. This isolates the various parts of your application, preventing you from shooting yourself in the foot with shared state, and helping you to separate concerns properly.\n\nThe arguments and return values of these modules fit into the following categories:\n\n- `Actions`: Functions you can call to perform a Control Action\n- `Observables`: Streams of transient events produced by Control Actions\n- `Replayables`: Streams of events which also store their last event, to be replayed to new subscribers\n\n#### Controls\n\n`(Actions, Replayables) -\u003e Observables`\n\nA Control specifies a group of Action functions. Action functions are the inputs to the app, being called in response to things like the user pressing buttons, navigating to a new page, or data arriving from the server.\n\nControl actions can cause side effects like making requests to a HTTP server. They can also call other actions - but only on subsequent ticks. For example, a `fetch` action may make a HTTP request, and then call an associated `response` action when the response is received.\n\nThe reason subsequent action calls must be on a separate tick is that Maxim's dispatcher prevents an Action from being run if another action is in progress. This makes your code easier to reason about, and makes it harder to produce unintended infinite loops. If you really need to call one action from within another (which you probably don't), you can do so in the next tick by using `setTimeout`, or by using Rx's `observeOn` method.\n\nEach Control can specify an `initialize` action, which is automatically called by Maxim once the app is ready to start.\n\nEach action function should emit an event by calling the trigger function bound to it's `this` keyword (optionally with parameters). e.g. `this(data)`. These events are then handled by models.\n\nFor an example Control, see `src/controls/NavigationControl.js` in `react-black-triangle`.\n\n#### Models\n\n`(Observables) -\u003e Replayables`\n\nEach Model file specifies a way to convert the various streams produced by your Control Actions into a single stream representing the values of the model over time.\n\nModels are not directly callable - they automatically produce new values when the Actions which they subscribe to are triggered. This is where RxJS comes in - Maxim passes your Models an [Rx.Observable](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md) for each of your Control Actions, which you can then manipulate using any of RxJS's methods to produce your return Rx.Observable.\n\nUnlike Control actions, the results of each model are cached by Maxim using an [Rx.ReplaySubject](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/subjects/replaysubject.md) so that their values persist through time. This makes the values of each model available to Reducers triggered by changes to other models in the future.\n\nFor an example Model, see `src/controls/NavigationModel.js` in `react-black-triangle`.\n\n#### Reducers\n\n`(Replayables) -\u003e Replayables`\n\nReducers watch changes to Models and other Reducers, combining their latest values into new data. For example, a Reducer could combine a Model of known resources and a Model indexing those resources into a list of indexed resources.\n\nReducers, like Models, return an [Rx.Observable](https://github.com/Reactive-Extensions/RxJS/blob/master/doc/api/core/observable.md). Maxim provides the Replayables produced by both Models and Reducers to Actors through the same object, so they cannot share the same names.\n\nExample reducer coming soon.\n\n#### Actors\n\n`(Actions, Replayables) -\u003e null`\n\nActors take the Replayables produced by Models and Reducers, subscribe to them, and then take some action based on the latest value (such as displaying a user interface, or calling more actions).\n\nFor an example Actor, see `src/actors/UserInterfaceActor.js` in `react-black-triangle`.\n\n### Initialization\n\nTo start a Maxim app, call `Maxim.initialize` with the Controls, Models, Reducers and Actors you'd like to include. For example:\n\n```\nimport {initialize} from \"maxim\";\nimport NavigationControl from \"./controls/NavigationControl\";\nimport NavigationModel from \"./models/NavigationModel\";\nimport UserInterfaceActor from \"./actors/UserInterfaceActor\";\n\nconst app = initialize({\n  controls: {\n    Navigation: NavigationControl,\n  },\n  models: {\n    Navigation: NavigationModel,\n  },\n  actors: [\n    UserInterface: UserInterfaceActor\n  ],\n});\n```\n\nObject keys specify the name with each module will be accessible in the `Actions`, `Observables` and `Replayables` objects passed as arguments to each module definition function.\n\n## License\n\nMaxim is MIT-licensed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesknelson%2Fmaxim","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamesknelson%2Fmaxim","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamesknelson%2Fmaxim/lists"}