
Research
Security News
Lazarus Strikes npm Again with New Wave of Malicious Packages
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
reactive-di
Advanced tools
Dependency injection with reactivity unobtrusive state-management in mobx manner, applied to react-like components, css-in-js. Compatible with flow, react, but free from framework lock-in (no React.createElement, Inferno.createVNode), etc. Size about 10kb reactive-di.min.js + 11kb lom_atom.min.js
example source, demo, todomvc benchmark
npm install --save reactive-di lom_atom babel-plugin-transform-metadata
Example .babelrc:
{
"presets": [
"flow",
"react",
["es2015", {"loose": true}]
],
"plugins": [
"transform-metadata",
"transform-decorators-legacy",
["transform-react-jsx", {"pragma": "lom_h"}]
]
}
babel-plugin-transform-metadata is optional, used for metadata generation.
Build rdi and copy to ../app-project/node_modules/reactive-di
npm run watch --reactive-di:dest=../app-project
Setup:
// @flow
import {mem, action} from 'lom_atom'
import {createReactWrapper, createCreateElement, Injector} from 'reactive-di'
import {render, h, Component} from 'preact'
function ErrorableView({error}: {error: Error}) {
return <div>
{error instanceof mem.Wait
? <div>
Loading...
</div>
: <div>
<h3>Fatal error !</h3>
<div>{error.message}</div>
<pre>
{error.stack.toString()}
</pre>
</div>
}
</div>
}
const lomCreateElement = createCreateElement(
createReactWrapper(
Component,
ErrorableView
),
h
)
global['lom_h'] = lomCreateElement
Usage:
class HelloContext {
@mem name = ''
}
function HelloView(
{prefix}: {prefix: string},
{context}: {context: HelloContext}
) {
return <div>
{prefix}, {context.name}
<br/><input value={context.name} onInput={
action((e: Event) => {
context.name = (e.target: any).value
})
} />
</div>
}
render(<HelloView prefix="Hello" />, document.body)
// @flow
function HelloView(
{prefix}: {prefix: string}, // props
{context}: {context: HelloContext} // automatically injected reactive context
) {
return <div>...</div>
}
Or
function HelloView(
_: {},
context: HelloComponent
) {
/// ...
}
Context signature generated from component via babel-plugin-transform-metadata.
Classes as keys. Without plugin, we need to define metadata manually:
HelloView.deps = [{context: HelloContext}]
Injector in createElement (lom_h) automatically initializes HelloContext and pass it to HelloView in
render(<HelloView prefix="Hello" />, document.body)
Rdi based on lom_atom, state management library, like mobx, but much simpler and with some killer features. Statefull or stateless components in rdi - pure functions.
Modifying state:
import {action, mem} from 'lom_atom'
class HelloContext {
@mem name = ''
}
function HelloView(
_: {},
{context}: {context: HelloContext}
) {
return <input value={context.name} onInput={
action((e: Event) => {
context.name = (e.target: any).value
})
} />
}
All state changes are asynchronous, but for prevent loosing cursor position in react input, action helper used.
Loading actual state:
import {mem, force} from 'lom_atom'
class HelloContext {
@force force: HelloContext
@mem set name(next: string | Error) {}
@mem get name(): string {
fetch('/hello')
.then((r: Response) => r.json())
.then((data: Object) => {
this.name = data.name
})
.catch((e: Error) => {
this.name = e
})
throw new mem.Wait()
}
}
function HelloView(
_: {},
context: HelloContext
) {
return <div>
<input value={context.name} onInput={
action((e: Event) => {
context.name = (e.target: any).value
})
} />
<button onClick={() => { context.forced.name }}>Reload from server</button>
</div>
}
First time context.name
invokes fetch('/hello') and actualizes state, second time - context.name
returns value from cache.
context.forced.name
invokes fetch handler again.
context.name = value
value sets directly into cache.
context.forced.name = value
invokes set name handler in HelloContext and sets into cache.
class HelloContext {
@mem get name() {
throw new Error('oops')
}
}
function HelloView(
_: {},
{context}: {context: HelloContext}
) {
return <input value={context.name} onInput={
action((e: Event) => {
context.name = (e.target: any).value
})
} />
}
Accessing context.name
throws oops, try/catch in HelloView wrapper displays default ErrorableView, registered in rdi:
// ...
function ErrorableView({error}: {error: Error}) {
return <div>
{error instanceof mem.Wait
? <div>
Loading...
</div>
: <div>
<h3>Fatal error !</h3>
<div>{error.message}</div>
<pre>
{error.stack.toString()}
</pre>
</div>
}
</div>
}
const lomCreateElement = createCreateElement(
createReactWrapper(
Component,
ErrorableView
),
h
)
global['lom_h'] = lomCreateElement
We can manually handle error:
function HelloView(
_: {},
{context}: {context: HelloContext}
) {
let name: string
try {
name = context.name
} catch (e) {
name = 'Error:' + e.message
}
return <input value={name} onInput={
action((e: Event) => {
context.name = (e.target: any).value
})
} />
}
Looks like error handling. throw new mem.Wait()
throws some specific exception.
class HelloContext {
@force force: HelloContext
@mem set name(next: string | Error) {}
@mem get name(): string {
fetch('/hello')
.then((r: Response) => r.json())
.then((data: Object) => {
this.name = data.name
})
.catch((e: Error) => {
this.name = e
})
throw new mem.Wait()
}
}
Catched in HelloComponent wrapper and default ErrorableView shows loader instead of HelloView.
function ErrorableView({error}: {error: Error}) {
return <div>
{error instanceof mem.Wait
? <div>
Loading...
</div>
: <div>
<h3>Fatal error !</h3>
<div>{error.message}</div>
<pre>
{error.stack.toString()}
</pre>
</div>
}
</div>
}
We can manually define loader in component, using try/catch:
function HelloView(
_: {},
{context}: {context: HelloContext}
) {
let name: string
try {
name = context.name
} catch (e) {
if (e instanceof mem.Wait) { name = 'Loading...' }
else { throw e }
}
return <input value={name} onInput={
action((e: Event) => {
context.name = (e.target: any).value
})
} />
}
class SomeAbstract {}
class SomeConcrete extends SomeAbstract {}
class C {
a: SomeAbstract
constructor(a: SomeAbstract) {
this.a = a
}
}
const injector = new Injector(
[
[SomeAbstract, new SomeConcrete()]
]
)
injector.value(SomeAbstract).a instanceof SomeConcrete
Creates slightly modified component.
import {cloneComponent} from 'reactive-di'
class FirstCounterService {
@mem value = 0
}
function CounterMessageView({value}: {value: string}) {
return <div>count: {value}</div>
}
function FirstCounterView(
_: {},
counter: FirstCounterService
) {
return <div>
<CounterMessageView value={counter.value}/>
<button id="FirstCounterAddButton" onClick={() => { counter.value++ }}>Add</button>
</div>
}
class SecondCounterService {
@mem value = 1
}
// Create FirstCounterView copy, but remove FirstCounterAddButton and replace FirstCounterService to SecondCounterService.
const SecondCounterView = cloneComponent(FirstCounterView, [
[FirstCounterService, SecondCounterService],
['FirstCounterAddButton', null],
], 'SecondCounterView')
class SharedService {}
function Parent(
props: {},
context: {sharedService: SharedService}
) {
return <Child parentService={context.sharedService} />
}
function Child(
context: {sharedService: SharedService}
) {
// context.sharedService instance cached in parent
}
class SharedService {}
function Parent() {
return <Child/>
}
function Child(
props: {},
context: {sharedService: SharedService}
) {
// sharedService - cached in child
}
Via adapters rdi supports css-in-js with reactivity and dependency injection power:
Setup:
// @flow
import {mem} from 'lom_atom'
import {createReactWrapper, createCreateElement, Injector} from 'reactive-di'
import {h, Component} from 'preact'
import {create as createJss} from 'jss'
import ErrorableView from './ErrorableView'
const jss = createJss()
/*
jss must implements IProcessor interface:
export interface IProcessor {
createStyleSheet<V: Object>(_cssObj: V, options: any): ISheet<V>;
}
export interface ISheet<V: Object> {
update(name?: string, props: V): ISheet<V>;
attach(): ISheet<V>;
detach(): ISheet<V>;
classes: {+[id: $Keys<V>]: string};
}
*/
const defaultDeps = []
const injector = new Injector(defaultDeps, jss)
const lomCreateElement = createCreateElement(
createReactWrapper(
Component,
ErrorableView,
injector
),
h
)
global['lom_h'] = lomCreateElement
Usage:
import {mem} from 'lom_atom'
import type {NamesOf} from 'lom_atom'
class ThemeVars {
@mem color = 'red'
}
function MyTheme(vars: ThemeVars) {
return {
wrapper: {
backgroundColor: vars.color
}
}
}
MyTheme.theme = true
function MyView(
props: {},
{theme, vars}: {theme: NamesOf<typeof MyTheme>, vars: ThemeVars}
) {
return <div class={theme.wrapper}>...
<button onClick={() => vars.color = 'green'}>Change color</button>
</div>
}
Styles automatically mounts/unmounts together with component. Changing vars.color
automatically rebuilds and remounts css.
Sometimes we need to pass component properties to its services.
import {mem, props} from 'lom_atom'
interface MyProps {
some: string;
}
class MyViewService {
@props _props: MyProps;
// @mem @props _props: MyProps; // for reactive props
@mem get some(): string {
return this._props.some + '-suffix'
}
}
function MyView(
props: MyProps,
{service}: {service: MyViewService}
) {
return <div>{service.some}</div>
}
We still can use any react/preact/inferno components together with rdi components.
import {defaultContext, BaseLogger} from 'lom_atom'
import type {ILogger} from 'lom_atom'
class Logger extends BaseLogger {
/**
* Invokes before atom creating
*
* @param host Object Object with atom
* @param field string property name
* @param key mixed | void for dictionary atoms - dictionary key
*/
create<V>(host: Object, field: string, key?: mixed): V | void {}
/**
* After atom destroy
*/
destroy(atom: IAtom<*>): void {}
/**
* Atom status changes
- 'waiting' - atom fetching from server (mem.Wait throwed)
- 'proposeToReap' - atom probably will be destroyed on next tick
- 'proposeToPull' - atom will be actualized on next tick
*/
status(status: ILoggerStatus, atom: IAtom<*>): void {}
/**
* Error while actualizing atom
*/
error<V>(atom: IAtom<V>, err: Error): void {}
/**
* Atom value changed
* @param isActualize bool if true - atom handler invoked, if false - only atom.cache value getted/setted
*/
newValue<V>(atom: IAtom<V>, from?: V | Error, to: V, isActualize?: boolean): void {}
}
defaultContext.setLogger(new Logger())
Configs maped to object properties by class names.
// @flow
import {mem} from 'lom_atom'
import {Injector} from 'reactive-di'
const defaultDeps = []
const injector = new Injector([], undefined, {
SomeService: {
name: 'test',
id: 123
}
})
class SomeService {
// setup babel-plugin-transform-metadata or define displayName, if js-uglify used
static displayName = 'SomeService'
@mem name = ''
id = 0
}
const someService: SomeService = injector.value(SomeService)
someService.name === 'test'
someService.id === 123
babel-plugin-transform-metadata can generate displayName. To enable it, add ["transform-metadata", {"addDisplayName": true}]
into .babelrc.
Example .babelrc:
{
"presets": [
"flow",
"react",
["es2015", {"loose": true}]
],
"plugins": [
["transform-metadata", {"addDisplayName": true}],
"transform-decorators-legacy",
["transform-react-jsx", {"pragma": "lom_h"}]
]
}
4.0.14 (2017-09-13)
<a name="4.0.13"></a>
FAQs
Reactive dependency injection
The npm package reactive-di receives a total of 2 weekly downloads. As such, reactive-di popularity was classified as not popular.
We found that reactive-di 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.
Research
Security News
The Socket Research Team has discovered six new malicious npm packages linked to North Korea’s Lazarus Group, designed to steal credentials and deploy backdoors.
Security News
Socket CEO Feross Aboukhadijeh discusses the open web, open source security, and how Socket tackles software supply chain attacks on The Pair Program podcast.
Security News
Opengrep continues building momentum with the alpha release of its Playground tool, demonstrating the project's rapid evolution just two months after its initial launch.