link-stack/packages/montar/README.md

4.7 KiB

montar

manage state in typescript. inspired by tolitius/mount from clojure.

What's this all about? Watch this video from Stuart Sierra to learn more about the background of component, mount, and montar.

Install

$ yarn add -D @digiresilience/montar

Usage

import { defState } from "@digiresilience/montar"

Creating State

// db.ts
const db = defState("db", {
  start: createDbConnection
})

export default db

where the createDbConnection function creates a connection (for example to a database) and is defined elsewhere.

Starting state

import { start } from "@digiresilience/montar"

const bootYourApp = async () => {
  // .. prepare for app boot
  // ..

  // boot!
  return start()
}

Using State

But wait, there is more.. this state is a top level being, which means it can be simply imported by other namespaces:

For example let's say an app needs a connection above. No problem:

// server.ts
import db from "./db"


async function doStuff() {
  return db.executeSql("foo")
}

Note that before start() is called, db will be uninitialized and therefore unusable. You must call start() before accessing any states.

Starting/Stopping State

montar has start and stop functions that will walk all the states created with defState and start / stop them accordingly: i.e. will call their start and stop defined functions.

When testing you might be interested in starting only certain states. You can do this with

  • startOnly(string[]) - only start the given states. WARNING: dependencies are not auto started, so you must ensure all dependencies are passed.
  • startWithout(string[]) - start all states except those passed

Start and Stop Order

Since dependencies are "injected" by requiring on the namespace level, montar trusts the typescript compiler and node.js runtime to maintain the start and stop order for all the defStates.

The "start" order is then recorded and replayed on each subsequent start

The "stop" order is simply the reverse of the start order.

Troubleshooting / Debugging

Set the DEBUG=montar environment variable for runtime debug logs.

Differences to mount

montar was created to bring management of top-level application state under control. It steals greedily from tolitius/mount a well-known library in the clojure and clojurescript community.

The problem solved is: how to load different sub-systems of your application while respecting dependency order? Also, how do achieve this without complicated dependency injection containers that make the code harder to read.

With montar once you define your state, then every other module can import and use your state without special syntax or wrappers.

montar differs from mount in these ways:

  • start/stop are async - the start and stop functions are async
  • no var rebinding - instead we use the little-known Proxy
    • this comes with limitations: only functions, arrays, objects can be defined as state. scalars do not work.
  • not concerned with reloading - in clj/cljs you have the repl and great hot-code reloading (thanks to immutable and persistent data structures). we do not have this in javascript.

Credits

Copyright © 2020-present Center for Digital Resilience

Contributors

Abel Luck
Abel Luck

License

License: AGPL v3

GNU AFFERO GENERAL PUBLIC LICENSE
Version 3, 19 November 2007

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Affero General Public License for more details.

You should have received a copy of the GNU Affero General Public License
along with this program.  If not, see <https://www.gnu.org/licenses/>.