Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

reactive-di

Package Overview
Dependencies
Maintainers
1
Versions
134
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

reactive-di - npm Package Compare versions

Comparing version 2.3.0 to 2.3.1

5

dist/annotations.js

@@ -44,4 +44,4 @@ 'use strict';

function factory(target) {
dm(_common.subtypeKey, 'func', target);
function factory(target, isJsx) {
dm(_common.subtypeKey, isJsx ? 'jsx' : 'func', target);
return target;

@@ -53,2 +53,3 @@ }

dm(_common.metaKey, new _common.ComponentMeta(rec || {}), target);
dm(_common.subtypeKey, 'jsx', target);
return target;

@@ -55,0 +56,0 @@ };

2

dist/core/common.js

@@ -156,3 +156,3 @@ 'use strict';

function isComponent(target) {
return typeof target === 'function' && gm(metaKey, target);
return typeof target === 'function' && gm(subtypeKey, target);
}

@@ -159,0 +159,0 @@

@@ -80,10 +80,29 @@ 'use strict';

var createElement = this._componentFactory.createElement;
var ce = this._componentFactory.createElement;
var createWrappedElement = function createWrappedElement(tag, props) {
for (var _len = arguments.length, children = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
children[_key - 2] = arguments[_key];
for (var _len = arguments.length, ch = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
ch[_key - 2] = arguments[_key];
}
return createElement(_this.wrapComponent(tag), props, children.length ? children : null);
switch (ch.length) {
case 0:
return ce(_this.wrapComponent(tag), props);
case 1:
return ce(_this.wrapComponent(tag), props, ch[0]);
case 2:
return ce(_this.wrapComponent(tag), props, ch[0], ch[1]);
case 3:
return ce(_this.wrapComponent(tag), props, ch[0], ch[1], ch[2]);
case 4:
return ce(_this.wrapComponent(tag), props, ch[0], ch[1], ch[2], ch[3]);
case 5:
return ce(_this.wrapComponent(tag), props, ch[0], ch[1], ch[2], ch[3], ch[4]);
case 6:
return ce(_this.wrapComponent(tag), props, ch[0], ch[1], ch[2], ch[3], ch[4], ch[5]);
case 7:
return ce(_this.wrapComponent(tag), props, ch[0], ch[1], ch[2], ch[3], ch[4], ch[5], ch[6]);
default:
return ce.apply(undefined, [_this.wrapComponent(tag), props].concat(ch));
}
};

@@ -90,0 +109,0 @@

{
"name": "reactive-di",
"version": "2.3.0",
"version": "2.3.1",
"description": "Reactive dependency injection",

@@ -5,0 +5,0 @@ "publishConfig": {

# reactive-di
Definitely complete solution for dependency injection, state-to-css, state-to-dom rendering, data loading, optimistic updates and rollbacks.
Solution for dependency injection and state-management, state-to-css, state-to-dom rendering, data loading, optimistic updates and rollbacks.
[Dependency injection](https://en.wikipedia.org/wiki/Dependency_injection) with [flowtype](http://flowtype.org/) support, based on [ds300 derivablejs](https://ds300.github.io/derivablejs/). For old browsers needs Map, Observable, Promise and optionally Reflect, Symbol polyfills.
## About
No statics, no singletones, abstract everything, configure everything.
Hierarchical scope, state management IoC container uses a class constructor or function signature to identify and inject its dependencies.
Features:
- Annotation based and highly flow-compatible
- Can resolve dependencies from [flowtype](http://flowtype.org/) interfaces, types, classes via [babel metadata plugin](https://github.com/zerkalica/babel-plugin-transform-metadata).
- Each dependency is [Derivable](http://ds300.github.io/derivablejs/#derivable-Derivable) or [Atom](http://ds300.github.io/derivablejs/#derivable-Atom)
- Can easily provide abstraction level on top of any state-to-dom manipulating library: [react](https://facebook.github.io/react/), [preact](https://preactjs.com/), [bel](https://github.com/shama/bel), [mercury](https://github.com/Raynos/mercury/), etc.
- Provide themes support via state-to-css library, like [jss](https://github.com/jsstyles/jss)
- Mimic to react: flow-compatible widgets with props autocomplete support, that looks like react, but without react dependencies
- Hierarchical - can create local state per widget, like in [angular2 di](https://angular.io/docs/ts/latest/guide/hierarchical-dependency-injection.html)
- Type based selectors
- Data loading via promises or observables
- Optimistic updates with rollbacks
- About 2500 SLOC with tests, 1000 without
- Suitable for both node and the browser
- Middlewares for functions and class methods
- Lifehooks onUpdate, onMount, onUnmount supported for any dependencies
There are many IoC containers for javascript, for example [inversify](http://inversify.io/), but reactive-di works without registering dependencies in container and has some state management features, like [mobx](http://mobxjs.github.io/mobx/).
## Flow
All dependencies presented as [atoms](http://ds300.github.io/derivablejs/#derivable-atom) or [derivables](http://ds300.github.io/derivablejs/#derivable-Derivable).
<img src="docs/flow.png" alt="reactive-di flow diagram" />
## Motivation
## Basic entities
We need good OO design with [Composition reuse](https://en.wikipedia.org/wiki/Composition_over_inheritance) and [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design) in complex javascript applications on sever and client.
- source({key, construct}) - atom source: model with data or service, can be injected from outside and changed in runtime.
- factory - mark function factory, if not used [babel metadata plugin](https://github.com/zerkalica/babel-plugin-transform-metadata)
- deps(...deps: mixed[]) - declare dependencies, if not used [babel metadata plugin](https://github.com/zerkalica/babel-plugin-transform-metadata)
- component({register: Function[]}(target) - any visual component
- theme - jss-like style
- updater(...updaters: Updater[]) - create loading status from updater services
- service - for optimizations, do not recalculate, if dependencies changed, only call constructor with new deps
Any stream is wrapper on top of domain data. We need to automate and move most of all reactive-data stream manipulations behind the scene. For example, [mobx](http://mobxjs.github.io/mobx/) is good there.
We need to reduce boilerplate code, by maximally using flow-types. Many decorators are unnecessary: use reflection metadata for classes, functions and components.
We need to keep all components clean and usable without di-framework: decorators must be used only for additional metadata, not as wrappers.
We need to remove dependencies at react-like frameworks from compiletime to runtime. It give posibility to create unified jsx-based zero-dependency component, which can be used in any jsx-compatible render-to-dom library.
We need to provide unified cssx-based component which can be used in any jss-compatible render-to-css library.
## Install
```
npm install reactive-di babel-plugin-transform-metadata
```
For using unified components, we need to define jsx pragma in transform-metadata:
.babelrc:
```json
{
"plugins": [
["transform-metadata", {
"jsxPragma": "__h"
}],
["transform-react-jsx", {
"pragma": "__h"
}]
]
}
```
reactive-di requires some polyfills: Promise, Observable (only if observables used in application code), Map, Proxy (only for middlewares).
## Basics
Reactive di container use classes or functions as unique identifiers of dependency.
```js
// @flow
import {Di} from 'reactive-di'
import type {Derivable} from 'reactive-di'
class Logger {
log(message: string): void {
console.log(message)
}
}
class TestClass {
constructor(logger: Logger) {
this._logger = logger
}
add(a: number): number {
this._logger.log(`calling add ${a} + 1`)
return a + 1
}
}
const di = new Di()
const testClass: Derivable<TestClass> = di.val(TestClass)
testClass.get().add(1)
```
## Architecture overview
<img src="https://rawgithub.com/zerkalica/reactive-di/master/docs/workflow-state.svg" alt="reactive-di flow diagram" />
## Sources
Source is [atom](http://ds300.github.io/derivablejs/#derivable-atom) with data object.
Source looks like pure data class with initial state. Source decorator can give some options: key: string - unique name of model class, this keys helps to associate models with data in json-object from prerender server.
```js
// @flow
import {source} from 'reactive-di/annotations'
interface UserRec {
id?: string;
name?: string;
}
@source({key: 'user'})
class User {
id: string
name: string
constructor(rec?: UserRec = {}) {
this.id = rec.id || ''
this.name = rec.name || ''
}
}
```
Or using reactive-di helper:
```js
//...
@source({key: 'user'})
class User extends BaseModel<UserRec>{
id: string
name: string
static defaults: UserRec = {
id: '',
name: ''
}
}
```
Source is updateable:
```js
// @flow
import {Di} from 'reactive-di'
// Updating source manually:
const userAtom = (new Di()).val(User)
userAtom.get() // User object
userAtom.set(new User(...))
```
## Service
Service is regular class or factory-functon with some actions: source manipulations.
```js
// @flow
import {Di} from 'reactive-di'
import {source} from 'reactive-di/annotations'
@source({key: 'user'})
class User {
id: string
name: string
}
class UserService {
_user: User
constructor(user: User) {
this._user = user
}
submit(): void {
}
}
// or as factory-function:
function createUserSubmit(user: User) {
return function userSubmit() {
// submit user
}
}
const userServiceAtom = (new Di()).val(UserService)
userServiceAtom.get().submit()
userAtom.set(new User(...))
// User changed --> UserService changed, get new service
userServiceAtom.get().submit()
```
Usually you don't need to listen Service changes in component, use service decorator to detach service from atom updates:
```js
// @flow
import {Di} from 'reactive-di'
import {service} from 'reactive-di/annotations'
@service
class UserService {
}
```
## Components
Component is function, where first argument is properties, second - is internal component state (dependencies), and third - is element factory: function(tag, props, children). In this form components does not depends on any react-like framework.
Di container injects state into each component by wrapping creteElement method, passed to each component function. Di does not use [react context](https://facebook.github.io/react/docs/context.html), this is only react-feature.
```js
// @flow
export type SrcComponent<Props, State> = (props: Props, state: State, h: ?((tag, props, children) => any)) => any
```
[babel metadata plugin](https://github.com/zerkalica/babel-plugin-transform-metadata) autodetects functions with jsx and places h argument automatically.
Example:
```js
// @flow
import React from 'react'
import {Di, ReactComponentFactory} from 'reactive-di'
interface UserProps {
id: string;
name: string;
}
interface UserState {
service: UserService;
}
export function User({id, name}: UserProps, {service}: UserState): mixed {
return <div>
User: {name}#{id}
<a href="#" onClick={service.edit}>[change]</a>
</div>
}
const di = new Di(new ReactComponentFactory(React))
const UserWithState: typeof User = di.wrapComponent(User)
React.render(<UserWithState id="1", name="2" />, document.body)
```
## Themes
Theme is dependency with [jss](https://github.com/cssinjs/jss) object and css class names. On first component mount - theme invokes factory, which passed to di options at entry point and attach css to dom. On last component unmount css part will be removed.
```js
// @flow
import {theme} from 'reactive-di/annotations'
@theme
class UserTheme {
wrapper: string
name: string
__css: mixed
constructor(deps: SomeDeps) {
this.__css {
wrapper: {
backgroundColor: 'white'
},
name: {
backgroundColor: 'red'
}
}
}
}
interface UserProps {
id: string;
name: string;
}
interface UserState {
service: UserService;
theme: UserTheme;
}
export function User({id, name}: UserProps, {theme, service}: UserState): mixed {
return <div className={theme.wrapper}>
User: <span className={theme.name}>{name}#{id}</span>
<a href="#" onClick={service.edit}>[change]</a>
</div>
}
```
## Lifecycles
Hooks used for handling component mount/unmount cycles and target updates. Where target - is any dependency, which hook belongs to. Hooks can be attached to any dependency, not only component. Components, which use this dependency, automatically update hook on first component mount and last component unmount.
```js
//@flow
export interface LifeCycle<Dep> {
/**
* Called on first mount of any component, which uses description
*/
onMount?: (dep: Dep) => void;
/**
* Called on last unmount of any component, which uses description
*/
onUnmount?: (dep: Dep) => void;
/**
* Called on Dep dependencies changes
*/
onUpdate?: (oldDep: Dep, newDep: Dep) => void;
}
```
Example:
```js
//@flow
import {hooks}
class UserService {
start(): void {
// subscribe to some observable
}
stop(): void {
// unsubscribe from observable
}
}
@hooks(UserService)
class UserServiceHooks {
/**
* Hooks is regular dependency: we can use injection in constructor
*/
constructor(deps: SomeDeps) {}
/**
* Called on first mount of any component, which use UserService
*/
onMount(userService: UserService): void {
userServiuce.start()
}
/**
* Called on last unmount of any component, which use UserService
*/
onUnmount(userService: UserService): void {
userService.stop()
}
/**
* Called on UserService constructor dependencies updates
*/
onUpdate(oldUserService: UserService, newUserService: UserService): void {
oldUserService.stop()
newUserService.start()
}
}
```
## Middlewares
Middlewares used in development for logging method calls and property get/set.
```js
// @flow
export interface ArgsInfo {
id: string;
type: string;
className: ?string;
propName: string;
}
export interface Middleware {
get?: <R>(value: R, info: ArgsInfo) => R;
set?: <R>(oldValue: R, newValue: R, info: ArgsInfo) => R;
exec?: <R>(resolve: (...args: any[]) => R, args: any[], info: ArgsInfo) => R;
}
```
```js
// @flow
import type {ArgsInfo, Middleware} from 'reactive-di'
class Mdl1 {
exec<R>(fn: (args: any[]) => R, args: any[], info: ArgsInfo): R {
console.log(`begin ${info.className ? 'method' : 'function'} ${info.id}`)
const result: R = fn(args)
console.log(`end ${info.id}`)
return result
}
get<R>(result: R, info: ArgsInfo): R {
console.log(`get ${info.id}: ${result}`)
return result
}
set<R>(oldValue: R, newValue: R, info: ArgsInfo): R {
console.log(`${info.id} changed from ${oldValue} to ${newValue}`)
return newValue
}
}
function createAdd(): (a: string) => {
return function add(a: string): string {
return a + 'b'
}
}
class Service {
add(a: string): string {
return a + 'b'
}
}
const di = (new Di()).middlewares([Mdl1])
// Function factories calls:
di.val(createAdd).get()('a')
// begin function add
// end add
// Class method calls
di.val(Service).get().add('a')
// begin method Service.add
// end Service.add
class TestClass {
a: string = '1'
}
const tc: TestClass = di.val(TestClass).get()
// Propery set/get:
tc.a
// get TestClass.a: 1
tc.a = '123'
// TestClass.a changed from 1 to 213
```
## Complete example

@@ -191,5 +575,4 @@

{children}: UserComponentProps,
{theme, user, loading, saving, service}: UserComponentState,
h
): mixed {
{theme, user, loading, saving, service}: UserComponentState
) {
if (loading.pending) {

@@ -229,104 +612,2 @@ return <div class={theme.wrapper}>Loading...</div>

## Middlewares
Middlewares used for development for logging method calls and property get/set.
```js
// @flow
export interface ArgsInfo {
id: string;
type: string;
className: ?string;
propName: string;
}
export interface Middleware {
types?: string[];
get?: <R>(value: R, info: ArgsInfo) => R;
set?: <R>(oldValue: R, newValue: R, info: ArgsInfo) => R;
exec?: <R>(resolve: (...args: any[]) => R, args: any[], info: ArgsInfo) => R;
}
```
Function factories calls:
```js
// @flow
import type {ArgsInfo, Middleware} from 'reactive-di'
class Mdl1 {
exec<R>(fn: (args: any[]) => R, args: any[], info: ArgsInfo): R {
console.log(`begin ${info.className ? 'method' : 'function'} ${info.id}`)
const result: R = fn(args)
console.log(`end ${info.id}`)
return result
}
}
function createAdd(): (a: string) => {
return function add(a: string): string {
return a + 'b'
}
}
const di = (new Di()).middlewares([Mdl1])
di.val(createAdd).get()('a')
// begin function add
// end add
```
Class method calls:
```js
// @flow
import type {ArgsInfo} from 'reactive-di'
class Mdl1 {
exec<R>(fn: (args: any[]) => R, args: any[], info: ArgsInfo): R {
console.log(`begin ${info.className ? 'method' : 'function'} ${info.id}`)
const result: R = fn(args)
console.log(`end ${info.id}`)
return result
}
}
class Service {
add(a: string): string {
return a + 'b'
}
}
const di = (new Di()).middlewares([Mdl1])
di.val(Service).get().add('a')
// begin method Service.add
// end Service.add
```
Propery set/get:
```js
// @flow
import type {ArgsInfo} from 'reactive-di'
class Mdl1 {
get<R>(result: R, info: ArgsInfo): R {
console.log(`get ${info.id}: ${result}`)
return result
}
set<R>(oldValue: R, newValue: R, info: ArgsInfo): R {
console.log(`${info.id} changed from ${oldValue} to ${newValue}`)
return newValue
}
}
const wrapper = new MiddlewareFactory([new Mdl1()])
class TestClass {
a: string = '1'
}
const tc: TestClass = di.val(TestClass).get()
tc.a
// get TestClass.a: 1
tc.a = '123'
// TestClass.a changed from 1 to 213
```
## Manifest

@@ -333,0 +614,0 @@

@@ -32,4 +32,4 @@ // @flow

export function factory<V: Function>(target: V): V {
dm(subtypeKey, 'func', target)
export function factory<V: Function>(target: V, isJsx?: boolean): V {
dm(subtypeKey, isJsx ? 'jsx' : 'func', target)
return target

@@ -41,2 +41,3 @@ }

dm(metaKey, new ComponentMeta(rec || {}), target)
dm(subtypeKey, 'jsx', target)
return target

@@ -43,0 +44,0 @@ }

@@ -130,3 +130,3 @@ // @flow

export function isComponent(target: Function): boolean {
return typeof target === 'function' && gm(metaKey, target)
return typeof target === 'function' && gm(subtypeKey, target)
}

@@ -133,0 +133,0 @@

@@ -74,9 +74,30 @@ // @flow

_getCreateElement(): CreateElement<*, *> {
const createElement = this._componentFactory.createElement
const ce = this._componentFactory.createElement
const createWrappedElement = (tag: Function, props?: ?{[id: string]: mixed}, ...children: any) => createElement(
this.wrapComponent(tag),
props,
children.length ? children : null
)
const createWrappedElement = (
tag: Function,
props?: ?{[id: string]: mixed},
...ch: any
) => {
switch (ch.length) {
case 0:
return ce(this.wrapComponent(tag), props)
case 1:
return ce(this.wrapComponent(tag), props, ch[0])
case 2:
return ce(this.wrapComponent(tag), props, ch[0], ch[1])
case 3:
return ce(this.wrapComponent(tag), props, ch[0], ch[1], ch[2])
case 4:
return ce(this.wrapComponent(tag), props, ch[0], ch[1], ch[2], ch[3])
case 5:
return ce(this.wrapComponent(tag), props, ch[0], ch[1], ch[2], ch[3], ch[4])
case 6:
return ce(this.wrapComponent(tag), props, ch[0], ch[1], ch[2], ch[3], ch[4], ch[5])
case 7:
return ce(this.wrapComponent(tag), props, ch[0], ch[1], ch[2], ch[3], ch[4], ch[5], ch[6])
default:
return ce(this.wrapComponent(tag), props, ...ch)
}
}

@@ -83,0 +104,0 @@ return createWrappedElement

@@ -15,6 +15,16 @@ // @flow

export interface LifeCycle<Dep> {
/**
* Called on first mount of any component, which uses description
*/
onMount?: (dep: Dep) => void;
/**
* Called on last unmount of any component, which uses description
*/
onUnmount?: (dep: Dep) => void;
onAfterUpdate?: (dep: Dep) => void;
/**
* Called on Dep dependencies changes
*/
onUpdate?: (oldDep: Dep, newDep: Dep) => void;
}

@@ -13,3 +13,2 @@ // @flow

export interface Middleware {
types?: string[];
get?: <R>(value: R, info: ArgsInfo) => R;

@@ -16,0 +15,0 @@ set?: <R>(oldValue: R, newValue: R, info: ArgsInfo) => R;

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc