resourcex v0.1.2
resourcex
resourcex - Utilities to wrap global variables into rxjs observables with reduced learning curve
This project provides a few handy utilities to make global variables slightly easier to work with, comparing to traditional Flux Architecture.
By using the utilities inside, plus some observable enhancement tools in your favorite MVVM framework, global store can be organized into a set of highly structured resources. Each resource exposes its value and corresponding mutating actions. In this way, frontend applications can integrate with backend RESTful API with less boilerplate.
Since this is a very light encapsulation on top of rxjs, anyone who doesn't mind delving into the design patterns of functional reactive programming can easily make something more elaborate from these basic tools.
Example
Both Vue and React examples can be found inside here.
// api.js
export async function getProfile() {
return { uid: 10086, name: 'ResouceX' };
}
export async function setName(name) {
return { success: true };
}
// resources.js
import { Resource } from 'resourcex';
import { getProfile, setName } from './api';
export const Profile = Resource(
{ uid: 0, name: '', version: 0 },
{
async get() {
const profile = await getProfile();
return { ...profile, version: 1 };
},
increaseVersion({ version }) {
return { version: version + 1 };
},
async setName(_, name) {
const { success } = await setName(name);
if (success) return { name };
else throw Error('update-failed');
},
},
);
Vue
For integrating with Vue, it's recommended to install vue-rx to ensure observable unregistrations are done correctly on unmount.
import { Profile } from '../resources';
new Vue({
el: '#app',
template: `
<div>
<h1>Hello, {{ profile$.name }}!</h1>
<h2>{{ profile$.uid }} called {{ profile$.version }} times</h2>
<button @click="setName('New Name')">Set New Name</button>
<button @click="incProfileVersion">Bump Version</button>
</div>
`,
subscriptions() {
return { profile$: Profile };
},
methods: {
setName(name) {
Profile.setName(name); // <- discard original return
},
async incProfileVersion() {
const { version } = await Profile.increaseVersion();
console.log(version); // <- captured original return
},
},
created() {
Profile.get();
},
});
React
For integrating with React, consider using resource-react-hook.
import React, { useEffect } from 'react';
import ReactDOM from 'react-dom';
import useResource from 'resource-react-hook';
import { Profile } from '../resources';
const App = () => {
const profile$ = useResource(Profile);
// on creation do a round of fetch
useEffect(() => {
Profile.get();
}, []);
return (
<div>
<h1>Hello, {profile$.name}!</h1>
<h2>
{profile$.uid} called {profile$.version} times
</h2>
<button onClick={() => Profile.setName('New Name')}>Set New Name</button>
<button onClick={Profile.increaseVersion}>Bump Version</button>
</div>
);
};
ReactDOM.render(<App />, document.getElementById('app'));
API
Resources
Resource(initial, actions?, middleware?, initHook?)
Creates a resource (as an rxjs
BehaviorSubject
) with (optionally)actions
defining how to change its value. Each action is a function that- Takes in as first parameter
state
, which is the current value of the subject - Returns a value which would be
Object.assign
ed into the originalstate
Therefore, the value
state
could be destructurally assigned. It could be left as_
or skipped with a comma if there's no dependency on previous value. Return value ofResource.action(..args)
respect the the original action function.document and examples for
middleware
andinitHook
are under construction hohoho- Takes in as first parameter
NaiveResource(initial)
- Almost the same as a
Resource
except that it only exposes aset
action
create a resource with an initial value, expose a
set
call to update its value- Almost the same as a
LocalStorageResource(localStorageKey, initial, actions?)
- Almost the same as a
Resource
, just that the value is persisted into local storage with key specified
- Almost the same as a
Operators
resourcex also exposes a few rxjs operators for easier use, esp in transforming DOM events:
catchMergeMap
/catchFlatMap
catchConcatMap
catchSwitchMap
catchExhaustMap
The are analagous to the counterparts mergeMap / flatMap
, concatMap
, switchMap
and exhaustMap
in rxjs.
They respect the same flattening strategy and simply wraps over them for error handling,
so that on error thrown the source observables are preserved.
E.g. click$
is the click event stream on a button,
the following line triggers Profile.get
call with exhaustive strategy:
click$.pipe(catchExhaustMap(Profile.get)).subscribe()
.subscribe()
is an empty subscription to kick start the streaming of events- Here is a good article explaining what flattening strategies rxjs provides
Change Log
- 0.1.2: Fix
NaiveResource
null returning bug (also the undocumented middleware API) - 0.1.1: Allow empty
actions
as pure, unmutatable global variable - 0.1.0: Major rewrite to return
Resource
asSubject
directly, and provide separate operators - 0.0.1 ~ 0.0.9: Pilot versions with
$
as exposedSubject
and no access to current state; not recommended anymore
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago
5 years ago