
Security News
Software Engineering Daily Podcast: Feross on AI, Open Source, and Supply Chain Risk
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.
micro reactive UI framework:
start method that returns an Observable of model instances from the reducer,tap and untap methods to create child sinks for child components.require their dependencies,together with the equally tiny but powerful yo-yo HTMLElement rendering library, RxJS, and any solid HTML framework such as Bootstrap or PureCss, rapidly build beautiful, powerful reactive web apps composed of loosely coupled, self-contained, single-purpose components.
although the examples in this document use yo-yo to render HTMLElements,
ÎĽrx is completely agnostic to the rendering engine.
in fact, ÎĽrx is compatible with any type of rendering library,
e.g. that render to log files, to a test mock, to a node stream, a websocket,
or even papyrus...
import newMurxPipe, { AsyncDiff, Renderer } from 'murx'
import { Observable, Observer } from 'rxjs'
const yo = require('yo-yo')
import debug = require('debug')
debug.enable('murx:*')
interface CounterModel {
value: number
disabled?: boolean
}
// create a new murx pipe
const murx = newMurxPipe<CounterModel>() // { diff$i, start, tap }, tap method not used in this example
const render: Functor<CounterModel,HTMLElement> =
({ value, disabled }: CounterModel) => yo
`<div class="col-sm-6">
<div class="card text-center">
<div class="card-block">
<h3 class="card-title">slow async counter</h3>
<p class="card-text">${value}</p>
<button type="button" class="btn btn-primary"
${disabled ? 'disabled' : ''} onclick=${onclick}>
<i class="fa ${disabled ? 'fa-spinner fa-spin' : 'fa-plus'}"></i> ${disabled ? 'processing...' : 'increment' }
</button>
</div>
</div>
</div>`
// apply the increment functor to the model on every click
const onclick = () => murx.diff$i.next(slowAsyncIncrement)
// AsyncDiff functors emit a sequence of states (model snapshots) based on a given state
const slowAsyncIncrement: AsyncDiff<CounterModel> = (model: CounterModel) =>
Observable.of({ disabled: false, value: model.value + 1 }) // increment counter...
.delay(1000) // ...after an imaginary slow async operation...
.startWith({ value: model.value, disabled: true }) // meanwhile, disable the button
const container = document.getElementById('murx-example-app')
// start the murx reducer !
const init = { value: 0 }
murx.start(init).do<CounterModel>(debug('murx:model:'))
.map(model => render(model)) // render the model
.scan((target, source) => yo.update(target, source)) // update the target element
.distinctUntilChanged() // yo-yo may occasionally return a new target element... (although only once in this example)
.forEach(counter => container.appendChild(counter)) // update the DOM for every new target element (again, only once here)
.catch(debug('murx:error:'))
the files of this example are available in this repository.
view a live version of this example in your browser console, or clone this repository and run the following commands from a terminal:
npm install
npm run example:simple
the above diagram illustrates how a murx-based app may be architectured. note that murx is completely agnostic to rendering: its API is limited to sinking async functors that are applied to model instances in the reducer, the output of which is available as a source of model instances.
in the proposed architecture, which is by no means imposed, the app component is composed of a murx pipe and its rendering function. likewise, child components are composed of a murx sink tapped off that of its parent component and of their rendering function.
parent components define wrap and unwrap functions
that respectively map child to parent model instances and vice-versa.
these functions are required to tap a child diff sink off the parent's sink:
when the child diff is applied, the parent model is first mapped
to its child scope. after applying the diff to the resulting child model,
the result is mapped back to the parent scope.
the unwrap functions may also be called by the rendering function
before calling the corresponding child rendering function.
this architecture ensures that each component is provided with a corresponding scoped view of the model stream, defined by its parent component. components are hence fully self-contained and may be composed as desired.
the architecture diagram introduces the diff$i async functor sink stream:
functors fed into the $diffi sink are applied
to the current state (model snapshot).
async functors are simply functions that map the current state
to an Observable sequence (stream) of states.
under the hood, the stream of async functors is input to a state reducer that simply applies each functor to the current state, and merges the output Observable sequence into the output state sequence.
in the above example, although slowAsyncIncrement is a pure function
(it has no side-effects), it still demonstrates how simple it is
to work with asynchronous processes.
in fact, slowAsyncIncrement could easily be replaced
by an asynchronous impure function:
because functors process state and return an Observable sequence of states,
they are ideal for triggering model-based side-effects,
in particular asynchronous side-effects,
e.g. to fetch data from an end-point into the model,
or store data from the model in a database,
or anything else really.
the simple example above is limited to rendering a single component.
wiring up an app with multiple components is just as easy.
components are just simple modules:
they require and manage their own component dependencies,
as illustrated by the (nearly) self-explanatory todo example,
in which the main application component requires a todo-card component.
the files of the todo example are available in this repository.
view a live version of this example in your browser console, or clone this repository and run the following commands from a terminal:
npm install
npm run example:todo
the todo example demonstrates one way of wiring an application.
however, the ÎĽrx API does not impose this choice.
here, we choose to export a factory that instantiates a rendering function.
the factory itself inputs a MurxSink instance and a map of event handlers.
handlers may be used to efficiently 'bubble-up' events from a child-component to a parent up its hierarchy that knows what to do with it.
as for the MurxSink instance, it is obtained from the tap method
of the main application's MurxSink instance:
the diff$i Observer of the returned MurxSink instance
is tapped off the diff$i Observer of the main application's MurxSink.
the tap method takes two function arguments:
unwrap maps a parent instance to a new child model instance,wrap maps a child model instance into a new parent instance.under the hood,
the diff$i Observer returned by the tap method
injects wrapped diff async functors into the parent diff$i observer.
wrapped diff$i functors are hence applied to the model reducer.
before applying the diff, the parent model is unwrapped into a child model.
after applying the diff and before injecting the result into the reducer,
the diff result is wrapped back into a new instance of the parent model.
an application hence only runs on a single state reducer instantiated by the topmost component, and each component may access its own scope within the global app state, and only its scope. ÎĽrx makes no assumptions on how a child is mapped from a parent, or vice-versa, and leaves full freedom to how scopes are defined.
events from a child component that should affect
the model outside of its scope are simply bubbled up and handled by
the appropriate parent component,
as illustrated in the todo example with the ondelete event.
note however, that state parameters should not be bubbled-up through handlers.
instead, if a child component requires partial access to a parent's scope,
the parent should enable such access through the unwrap and wrap functions.
use of handlers should be restricted to events that must be handled further up
the hierarchy. when processed, these events might, or might not result
in a modification of state.
note that the tap method subscribes the parent diff$i Observer
to that of the returned child instance.
the subscription may be released with the latter's untap method,
after which the child instance may be disregarded.
this should be done by the child instance's parent instance,
that instantiated the former, and hence manages its life cycle,
as illustrated in the example.
ES5 and Typescript compatible.
coded in Typescript 2, transpiled to ES5.
type MurxPipeFactory = <M>() => MurxPipe<M>
interface MurxPipe<M> extends MurxSink<M> {
start (value: M): Observable<M>
}
interface MurxSink<M> {
diff$i: Observer<AsyncFunctor<M,M>>
tap <S>(unwrap: Functor<M,S>, wrap: (parent: M, child: S) => M): MurxSink<S>
untap (): void // release the internal subscription of the parent Observer to that of this instance, if any.
}
type AsyncFunctor<T,U> = Functor<T,Observable<U>>
type Functor<T,U> = (val: T) => U
the following generic types are provided for convenience, when working with any type of renderer, but are not required by murx:
type ComponentFactory<M, H extends Handlers, S, C> =
(sink: MurxSink<M>, handlers?: Partial<H>, opts?: Partial<S>) => C
interface Handlers {
[ key:string ]: (...args: any[]) => void
}
that's it... go murx your app!
for a detailed specification of the API,
run the unit tests in your browser.
see the contribution guidelines
Copyright 2017 Stéphane M. Catala
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and Limitations under the License.
FAQs
micro reactive UI framework
We found that murx demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Socket CEO Feross Aboukhadijeh joins Software Engineering Daily to discuss modern software supply chain attacks and rising AI-driven security risks.

Security News
GitHub has revoked npm classic tokens for publishing; maintainers must migrate, but OpenJS warns OIDC trusted publishing still has risky gaps for critical projects.

Security News
Rust’s crates.io team is advancing an RFC to add a Security tab that surfaces RustSec vulnerability and unsoundness advisories directly on crate pages.