├── index.js
├── sample
└── basic
│ ├── public
│ ├── favicon.ico
│ ├── manifest.json
│ └── index.html
│ ├── src
│ ├── Home.js
│ ├── index.js
│ ├── services
│ │ └── async.js
│ ├── registry.js
│ ├── App.js
│ ├── reser
│ │ ├── services
│ │ │ ├── util
│ │ │ │ ├── proto.js
│ │ │ │ └── index.js
│ │ │ ├── storage
│ │ │ │ ├── local.js
│ │ │ │ └── index.js
│ │ │ └── store.js
│ │ ├── loader.js
│ │ ├── provider.js
│ │ ├── collection.js
│ │ └── index.js
│ └── serviceWorker.js
│ ├── .gitignore
│ ├── package.json
│ └── README.md
├── babel.config.js
├── .eslintrc.json
├── src
├── provider.js
├── services
│ ├── storage
│ │ ├── index.js
│ │ └── local.js
│ ├── util
│ │ ├── proto.js
│ │ └── index.js
│ └── store.js
├── collection.js
├── loader.js
└── index.js
├── LICENSE
├── .gitignore
├── package.json
└── README.md
/index.js:
--------------------------------------------------------------------------------
1 | exports = module.exports = require('./lib').default
--------------------------------------------------------------------------------
/sample/basic/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ethan0007/reser/master/sample/basic/public/favicon.ico
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true)
3 | return {
4 | presets: [
5 | '@babel/preset-env',
6 | '@babel/preset-react'
7 | ],
8 | plugins: [
9 | '@babel/plugin-syntax-dynamic-import',
10 | '@babel/plugin-proposal-class-properties'
11 | ]
12 | }
13 | }
--------------------------------------------------------------------------------
/sample/basic/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | }
10 | ],
11 | "start_url": ".",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/sample/basic/src/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withService } from './reser';
3 |
4 | function Home(props) {
5 | const { async } = props.services
6 | if (async) console.log(async);
7 | return
8 |
Home
9 |
10 | {
11 | async &&
12 |
13 | }
14 |
15 |
16 | }
17 |
18 | export default withService('async')(Home)
--------------------------------------------------------------------------------
/sample/basic/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/sample/basic/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 | import * as serviceWorker from './serviceWorker';
5 |
6 | ReactDOM.render(, document.getElementById('root'));
7 |
8 | // If you want your app to work offline and load faster, you can change
9 | // unregister() to register() below. Note this comes with some pitfalls.
10 | // Learn more about service workers: https://bit.ly/CRA-PWA
11 | serviceWorker.unregister();
12 |
--------------------------------------------------------------------------------
/sample/basic/src/services/async.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { withNewService } from '../reser'
3 |
4 | function name(state = { asyncName: 'Foo' }, action) {
5 | return state
6 | }
7 |
8 | function AsyncPage(props) {
9 | return Async View
10 | }
11 |
12 | function NewService() {
13 | this.name = 'Kevin'
14 | }
15 |
16 | function registry(services) {
17 | services.add(NewService, 'newservice')
18 | }
19 |
20 | export default class Async {
21 |
22 | static reducer = name
23 | static persist = true
24 |
25 | title = 'Async Service'
26 |
27 | View = withNewService(registry)(AsyncPage)
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/sample/basic/src/registry.js:
--------------------------------------------------------------------------------
1 |
2 | import { UtilService } from './reser'
3 |
4 | function hello(state = { name: 'default' }, action) {
5 | return state
6 | }
7 |
8 | class Test {
9 | static service = 'test'
10 | static reducer = hello
11 | static persist = true
12 | static mergetest = {
13 | b: 2
14 | }
15 | name = 'Test'
16 | }
17 |
18 | const FuncService = function (params) {
19 | this.name = 'Func'
20 | }
21 |
22 | FuncService.mergetest = {
23 | a: 1
24 | }
25 |
26 | export default function (services) {
27 |
28 | services.add(Test)
29 |
30 | services.add(FuncService, 'func')
31 |
32 | services.add(() => import('./services/async'), 'async')
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": [
8 | "eslint:recommended"
9 | ],
10 | "parser": "babel-eslint",
11 | "globals": {
12 | "Atomics": "readonly",
13 | "SharedArrayBuffer": "readonly"
14 | },
15 | "parserOptions": {
16 | "ecmaVersion": 2018,
17 | "sourceType": "module"
18 | },
19 | "plugins": [
20 | "react-hooks"
21 | ],
22 | "rules": {
23 | "quotes": [
24 | "error",
25 | "single"
26 | ],
27 | "semi": [
28 | "error",
29 | "never"
30 | ],
31 | "react-hooks/rules-of-hooks": "error",
32 | "react-hooks/exhaustive-deps": "warn"
33 | }
34 | }
--------------------------------------------------------------------------------
/src/provider.js:
--------------------------------------------------------------------------------
1 | import { ServiceProvider as BaseServiceProvider } from 'jservice'
2 | import Loader from './loader'
3 |
4 | class ServiceProvider extends BaseServiceProvider {
5 |
6 | createServices(serviceNames, callback) {
7 | let services = {}
8 | for (let i = 0; i < serviceNames.length; i++) {
9 | const name = serviceNames[i]
10 | let service = this.service(name)
11 | if (service instanceof Loader) {
12 | if (!service.value)
13 | service.load(true).then(isNew => isNew && callback())
14 | service = service.value
15 | }
16 | services[name] = service
17 | }
18 | return services
19 | }
20 |
21 | }
22 |
23 | export default ServiceProvider
24 |
--------------------------------------------------------------------------------
/src/services/storage/index.js:
--------------------------------------------------------------------------------
1 | import Local from './local'
2 |
3 | class Storage {
4 | static service = 'storage'
5 |
6 | adapter = null
7 |
8 | constructor(provider, config) {
9 | const result = typeof config === 'function' ?
10 | config() : this._defaultConfig()
11 | this.adapter = result.adapter
12 | }
13 |
14 | _defaultConfig() {
15 | return {
16 | adapter: new Local()
17 | }
18 | }
19 |
20 | setItem(key, val) {
21 | return this.adapter.setItem(key, val)
22 | }
23 |
24 | getItem(key) {
25 | return this.adapter.getItem(key)
26 | }
27 |
28 | getAllKeys() {
29 | return this.adapter.getAllKeys()
30 | }
31 |
32 | }
33 |
34 | export default Storage
35 |
--------------------------------------------------------------------------------
/sample/basic/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "basic",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react-dom": "^16.8.6",
7 | "react-scripts": "3.0.1"
8 | },
9 | "scripts": {
10 | "start": "react-scripts start",
11 | "build": "react-scripts build",
12 | "test": "react-scripts test",
13 | "eject": "react-scripts eject"
14 | },
15 | "eslintConfig": {
16 | "extends": "react-app"
17 | },
18 | "browserslist": {
19 | "production": [
20 | ">0.2%",
21 | "not dead",
22 | "not op_mini all"
23 | ],
24 | "development": [
25 | "last 1 chrome version",
26 | "last 1 firefox version",
27 | "last 1 safari version"
28 | ]
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/src/services/storage/local.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * A polyfill for asyncStorage that uses localStorage
4 | */
5 |
6 | class Storage {
7 |
8 | constructor() {
9 | try {
10 | localStorage.setItem('____', '')
11 | localStorage.removeItem('____')
12 | this.available = true
13 | } catch (e) {
14 | this.available = false
15 | }
16 | }
17 |
18 | setItem(key, val) {
19 | if (this.available) localStorage.setItem(key, val)
20 | return Promise.resolve()
21 | }
22 |
23 | getItem(key) {
24 | let val = null
25 | if (this.available) val = localStorage.getItem(key)
26 | return Promise.resolve(val)
27 | }
28 |
29 | getAllKeys() {
30 | return Promise.resolve(this.available ?
31 | Object.keys(localStorage) : [])
32 | }
33 |
34 | }
35 |
36 | export default Storage
37 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 RhaldKhein
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | **/.DS_Store
2 | lib
3 | scratch
4 | sample/**/engine
5 |
6 | # Logs
7 | logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 |
13 | # Runtime data
14 | pids
15 | *.pid
16 | *.seed
17 | *.pid.lock
18 |
19 | # Directory for instrumented libs generated by jscoverage/JSCover
20 | lib-cov
21 |
22 | # Coverage directory used by tools like istanbul
23 | coverage
24 |
25 | # nyc test coverage
26 | .nyc_output
27 |
28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
29 | .grunt
30 |
31 | # Bower dependency directory (https://bower.io/)
32 | bower_components
33 |
34 | # node-waf configuration
35 | .lock-wscript
36 |
37 | # Compiled binary addons (https://nodejs.org/api/addons.html)
38 | build/Release
39 |
40 | # Dependency directories
41 | node_modules/
42 | jspm_packages/
43 |
44 | # TypeScript v1 declaration files
45 | typings/
46 |
47 | # Optional npm cache directory
48 | .npm
49 |
50 | # Optional eslint cache
51 | .eslintcache
52 |
53 | # Optional REPL history
54 | .node_repl_history
55 |
56 | # Output of 'npm pack'
57 | *.tgz
58 |
59 | # Yarn Integrity file
60 | .yarn-integrity
61 |
62 | # dotenv environment variables file
63 | .env
64 |
65 | # next.js build output
66 | .next
--------------------------------------------------------------------------------
/src/services/util/proto.js:
--------------------------------------------------------------------------------
1 | import _set from 'lodash.set'
2 |
3 | export function get(obj, path, defaultValue = null) {
4 | return String.prototype.split.call(path, /[,[\].]+?/)
5 | .filter(Boolean)
6 | .reduce((a, c) => (Object.hasOwnProperty.call(a, c) ? a[c] : defaultValue), obj)
7 | }
8 |
9 | export function set(obj, val) {
10 | return _set(obj, val)
11 | }
12 |
13 | export function pick(object, keys) {
14 | return keys.reduce((obj, key) => {
15 | if (object && object.hasOwnProperty(key)) {
16 | obj[key] = object[key]
17 | }
18 | return obj
19 | }, {})
20 | }
21 |
22 | export function isEmpty(obj) {
23 | return [Object, Array].includes((obj || {}).constructor) && !Object.entries((obj || {})).length
24 | }
25 |
26 | export function mapObject(object, mapFn) {
27 | return Object.keys(object).reduce(function (result, key) {
28 | result[key] = mapFn(object[key])
29 | return result
30 | }, {})
31 | }
32 |
33 | export function isFunction(fn) {
34 | return typeof fn === 'function'
35 | }
36 |
37 | export function isConstructor(fn) {
38 | return typeof fn === 'function' && fn.hasOwnProperty('prototype')
39 | }
40 |
41 | export function invert(obj) {
42 | let ret = {}
43 | for (let key in obj) ret[obj[key]] = key
44 | return ret
45 | }
--------------------------------------------------------------------------------
/sample/basic/src/App.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withContainer } from './reser'
3 | import registry from './registry'
4 | import { BrowserRouter as Router, Route, Link } from "react-router-dom"
5 | import Home from './Home'
6 |
7 | function Index() {
8 | return Home
;
9 | }
10 |
11 | function About() {
12 | return About
;
13 | }
14 |
15 | function Users() {
16 | return Users
;
17 | }
18 |
19 | function App({ container }) {
20 | // console.log(container)
21 | const util = container.provider.get('util')
22 | const res = util.getStatics('mergetest', {
23 | merge: true
24 | })
25 | console.log(res);
26 | util.loadAsyncServices().then(res => {
27 | console.log(res)
28 | })
29 | return (
30 | container.isReady &&
31 |
32 |
33 |
34 | Reser
35 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | )
60 | }
61 |
62 | export default withContainer(registry)(App);
63 |
--------------------------------------------------------------------------------
/src/collection.js:
--------------------------------------------------------------------------------
1 | import { ServiceCollection as BaseServiceCollection } from 'jservice'
2 | import { isFunction, isConstructor } from './services/util/proto'
3 | import Loader from './loader'
4 |
5 | class ServiceCollection extends BaseServiceCollection {
6 |
7 | constructor(core) {
8 | super(core)
9 | this._core = core
10 | }
11 |
12 | _validate(service) {
13 | let err = null
14 | if (service.async) err = 'async'
15 | // Add more static validation
16 | if (err) throw new Error(`Invalid service signature "${err}"`)
17 | }
18 |
19 | _push(Service, name, config, skip) {
20 | this._validate(Service)
21 | // Check if service is async or not
22 | if (isFunction(Service) && !isConstructor(Service)) {
23 | // Async service
24 | Service.service = name
25 | const loader = new Loader(this._core.provider, Service, name, config)
26 | const LoaderService = () => loader
27 | LoaderService.type = Service.type
28 | LoaderService.service = name
29 | LoaderService.async = true
30 | Service = LoaderService
31 | }
32 | super._push(Service, name, config, skip)
33 | }
34 |
35 | isAsyncService(name) {
36 | const { names, services } = this
37 | for (const key in names) {
38 | if (names.hasOwnProperty(key) && key === name) {
39 | return services[names[key]].async || false
40 | }
41 | }
42 | return true
43 | }
44 |
45 | scoped() {
46 | throw new Error('Scoped services are not supported yet')
47 | }
48 |
49 | transient() {
50 | throw new Error('Transient services are not supported yet')
51 | }
52 |
53 | }
54 |
55 | export default ServiceCollection
56 |
--------------------------------------------------------------------------------
/src/services/util/index.js:
--------------------------------------------------------------------------------
1 | import * as proto from './proto'
2 |
3 | class Util {
4 | static service = 'util'
5 | _core = null
6 |
7 | constructor(provider, custom) {
8 | this._core = provider.get('__core__')
9 | for (const key in custom) {
10 | if (this[key]) {
11 | // eslint-disable-next-line no-console
12 | console.warn(`Built-in util can't add existing "${key}"`)
13 | continue
14 | }
15 | this[key] = custom[key]
16 | }
17 | }
18 |
19 | getStatics(prop, options = {}) {
20 | let { names, services } = this._core.collection
21 | let result = {}
22 | names = this.invert(names)
23 | for (let i = 0; i < services.length; i++) {
24 | let service = services[i]
25 | if (service.async) {
26 | service = service()._service
27 | if (!service) continue
28 | }
29 | let value = service[prop]
30 | if (value === undefined) continue
31 | if (options.merge) {
32 | Object.assign(result, value)
33 | continue
34 | }
35 | result[names[i]] = value
36 | }
37 | return result
38 | }
39 |
40 | loadAsyncServices(...serviceNames) {
41 | let { names, services } = this._core.collection
42 | let toLoad = []
43 | names = this.invert(names)
44 | for (let i = 0; i < services.length; i++) {
45 | let service = services[i]
46 | if (!service.async) continue
47 | service = service()
48 | if (serviceNames.length &&
49 | serviceNames.indexOf(names[i]) === -1)
50 | continue
51 | toLoad.push(service.load())
52 | }
53 | return Promise.all(toLoad)
54 | }
55 |
56 | }
57 |
58 | Object.assign(Util.prototype, proto)
59 |
60 | export default Util
--------------------------------------------------------------------------------
/sample/basic/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
22 | React App
23 |
24 |
25 |
26 |
27 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/loader.js:
--------------------------------------------------------------------------------
1 | import { isFunction, isConstructor } from './services/util/proto'
2 |
3 | class Loader {
4 |
5 | constructor(provider, loader, name, config) {
6 | this._provider = provider
7 | this._loader = loader
8 | this._name = name
9 | this._config = config
10 | this._service = null
11 | this._loading = null
12 | this.value = null
13 | }
14 |
15 | _fetch() {
16 | if (this._service) return Promise.resolve(this._service)
17 | if (!this._loader)
18 | throw new Error('Async loader is not referenced to a service')
19 | return this._loader()
20 | .then(Service => {
21 | this._service = Service.default || Service
22 | return this._service
23 | })
24 | }
25 |
26 | load(onlyNew) {
27 | if (this.value && onlyNew) return Promise.resolve()
28 | if (this._loading) {
29 | if (onlyNew) return Promise.resolve()
30 | return this._loading
31 | }
32 | if (this.value) return Promise.resolve(this.value)
33 | // Continue fetching and creating instance
34 | this._loading = this._fetch()
35 | .then(Service => {
36 | let result
37 | if (isConstructor(Service)) {
38 | result = new Service(
39 | this._provider,
40 | this._config && this._config(this._provider)
41 | )
42 | } else if (isFunction(Service)) {
43 | result = Service()
44 | } else {
45 | result = Service
46 | }
47 | this.value = result
48 | this._loading = null
49 | if (Service.persist) {
50 | return this._provider.service('store')
51 | ._persistService(Service, this._name)
52 | }
53 | })
54 | .then(() => this.value)
55 | return this._loading
56 | }
57 |
58 | }
59 |
60 | export default Loader
61 |
--------------------------------------------------------------------------------
/sample/basic/src/reser/services/util/proto.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.get = get;
7 | exports.set = set;
8 | exports.pick = pick;
9 | exports.isEmpty = isEmpty;
10 | exports.mapObject = mapObject;
11 | exports.isFunction = isFunction;
12 | exports.isConstructor = isConstructor;
13 | exports.invert = invert;
14 |
15 | var _lodash = _interopRequireDefault(require("lodash.set"));
16 |
17 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
18 |
19 | function get(obj, path) {
20 | var defaultValue = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
21 | return String.prototype.split.call(path, /[,[\].]+?/).filter(Boolean).reduce(function (a, c) {
22 | return Object.hasOwnProperty.call(a, c) ? a[c] : defaultValue;
23 | }, obj);
24 | }
25 |
26 | function set(obj, val) {
27 | return (0, _lodash["default"])(obj, val);
28 | }
29 |
30 | function pick(object, keys) {
31 | return keys.reduce(function (obj, key) {
32 | if (object && object.hasOwnProperty(key)) {
33 | obj[key] = object[key];
34 | }
35 |
36 | return obj;
37 | }, {});
38 | }
39 |
40 | function isEmpty(obj) {
41 | return [Object, Array].includes((obj || {}).constructor) && !Object.entries(obj || {}).length;
42 | }
43 |
44 | function mapObject(object, mapFn) {
45 | return Object.keys(object).reduce(function (result, key) {
46 | result[key] = mapFn(object[key]);
47 | return result;
48 | }, {});
49 | }
50 |
51 | function isFunction(fn) {
52 | return typeof fn === 'function';
53 | }
54 |
55 | function isConstructor(fn) {
56 | return typeof fn === 'function' && fn.hasOwnProperty('prototype');
57 | }
58 |
59 | function invert(obj) {
60 | var ret = {};
61 |
62 | for (var key in obj) {
63 | ret[obj[key]] = key;
64 | }
65 |
66 | return ret;
67 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "reser",
3 | "version": "0.0.0",
4 | "description": "An asynchronous DI framework for modular React and React-Native",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "build": "npm run clean && babel src --out-dir lib",
8 | "watch": "npm run clean && babel src --watch --out-dir lib",
9 | "watch-basic": "npm run clean && babel src --watch --out-dir sample/basic/src/reser",
10 | "clean": "rm -rf lib && rm -rf sample/basic/src/reser",
11 | "prepare": "npm run build",
12 | "test": "echo \"Error: no test specified\" && exit 1"
13 | },
14 | "repository": {
15 | "type": "git",
16 | "url": "git+https://github.com/rhaldkhein/reser.git"
17 | },
18 | "author": "RhaldKhein",
19 | "license": "MIT",
20 | "bugs": {
21 | "url": "https://github.com/rhaldkhein/reser/issues"
22 | },
23 | "homepage": "https://github.com/rhaldkhein/reser#readme",
24 | "files": [
25 | "lib",
26 | "src"
27 | ],
28 | "dependencies": {
29 | "jservice": "^0.1.22",
30 | "lodash.set": "^4.3.2"
31 | },
32 | "devDependencies": {
33 | "@babel/cli": "^7.5.0",
34 | "@babel/core": "^7.4.5",
35 | "@babel/plugin-proposal-class-properties": "^7.4.4",
36 | "@babel/plugin-syntax-dynamic-import": "^7.2.0",
37 | "@babel/preset-env": "^7.4.5",
38 | "@babel/preset-es2016": "^7.0.0-beta.53",
39 | "@babel/preset-react": "^7.0.0",
40 | "babel-eslint": "^10.0.1",
41 | "babel-loader": "^8.0.6",
42 | "eslint": "^5.16.0",
43 | "eslint-plugin-react-hooks": "^1.6.0",
44 | "node-localstorage": "^1.3.1",
45 | "react": "^16.8.6",
46 | "react-redux": "^7.1.0",
47 | "react-router-dom": "^5.0.1",
48 | "redux": "^4.0.4"
49 | },
50 | "keywords": [
51 | "react",
52 | "react native",
53 | "framework",
54 | "modular",
55 | "services",
56 | "di",
57 | "ioc",
58 | "di container",
59 | "dependency injection"
60 | ]
61 | }
62 |
--------------------------------------------------------------------------------
/sample/basic/src/reser/services/storage/local.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
9 |
10 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
11 |
12 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
13 |
14 | /**
15 | * A polyfill for asyncStorage that uses localStorage
16 | */
17 | var Storage =
18 | /*#__PURE__*/
19 | function () {
20 | function Storage() {
21 | _classCallCheck(this, Storage);
22 |
23 | try {
24 | localStorage.setItem('____', '');
25 | localStorage.removeItem('____');
26 | this.available = true;
27 | } catch (e) {
28 | this.available = false;
29 | }
30 | }
31 |
32 | _createClass(Storage, [{
33 | key: "setItem",
34 | value: function setItem(key, val) {
35 | if (this.available) localStorage.setItem(key, val);
36 | return Promise.resolve();
37 | }
38 | }, {
39 | key: "getItem",
40 | value: function getItem(key) {
41 | var val = null;
42 | if (this.available) val = localStorage.getItem(key);
43 | return Promise.resolve(val);
44 | }
45 | }, {
46 | key: "getAllKeys",
47 | value: function getAllKeys() {
48 | return Promise.resolve(this.available ? Object.keys(localStorage) : []);
49 | }
50 | }]);
51 |
52 | return Storage;
53 | }();
54 |
55 | var _default = Storage;
56 | exports["default"] = _default;
--------------------------------------------------------------------------------
/sample/basic/src/reser/services/storage/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _local = _interopRequireDefault(require("./local"));
9 |
10 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
11 |
12 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
13 |
14 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
15 |
16 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
17 |
18 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
19 |
20 | var Storage =
21 | /*#__PURE__*/
22 | function () {
23 | function Storage(provider, config) {
24 | _classCallCheck(this, Storage);
25 |
26 | _defineProperty(this, "adapter", null);
27 |
28 | var result = typeof config === 'function' ? config() : this._defaultConfig();
29 | this.adapter = result.adapter;
30 | }
31 |
32 | _createClass(Storage, [{
33 | key: "_defaultConfig",
34 | value: function _defaultConfig() {
35 | return {
36 | adapter: new _local["default"]()
37 | };
38 | }
39 | }, {
40 | key: "setItem",
41 | value: function setItem(key, val) {
42 | return this.adapter.setItem(key, val);
43 | }
44 | }, {
45 | key: "getItem",
46 | value: function getItem(key) {
47 | return this.adapter.getItem(key);
48 | }
49 | }, {
50 | key: "getAllKeys",
51 | value: function getAllKeys() {
52 | return this.adapter.getAllKeys();
53 | }
54 | }]);
55 |
56 | return Storage;
57 | }();
58 |
59 | _defineProperty(Storage, "service", 'storage');
60 |
61 | var _default = Storage;
62 | exports["default"] = _default;
--------------------------------------------------------------------------------
/sample/basic/src/reser/loader.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _proto = require("./services/util/proto");
9 |
10 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
11 |
12 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
13 |
14 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
15 |
16 | var Loader =
17 | /*#__PURE__*/
18 | function () {
19 | function Loader(provider, loader, name, config) {
20 | _classCallCheck(this, Loader);
21 |
22 | this._provider = provider;
23 | this._loader = loader;
24 | this._name = name;
25 | this._config = config;
26 | this._service = null;
27 | this._loading = null;
28 | this.value = null;
29 | }
30 |
31 | _createClass(Loader, [{
32 | key: "_fetch",
33 | value: function _fetch() {
34 | var _this = this;
35 |
36 | if (this._service) return Promise.resolve(this._service);
37 | if (!this._loader) throw new Error('Async loader is not referenced to a service');
38 | return this._loader().then(function (Service) {
39 | _this._service = Service["default"] || Service;
40 | return _this._service;
41 | });
42 | }
43 | }, {
44 | key: "load",
45 | value: function load(onlyNew) {
46 | var _this2 = this;
47 |
48 | if (this.value && onlyNew) return Promise.resolve();
49 |
50 | if (this._loading) {
51 | if (onlyNew) return Promise.resolve();
52 | return this._loading;
53 | }
54 |
55 | if (this.value) return Promise.resolve(this.value); // Continue fetching and creating instance
56 |
57 | this._loading = this._fetch().then(function (Service) {
58 | var result;
59 |
60 | if ((0, _proto.isConstructor)(Service)) {
61 | result = new Service(_this2._provider, _this2._config && _this2._config(_this2._provider));
62 | } else if ((0, _proto.isFunction)(Service)) {
63 | result = Service();
64 | } else {
65 | result = Service;
66 | }
67 |
68 | _this2.value = result;
69 | _this2._loading = null;
70 |
71 | if (Service.persist) {
72 | return _this2._provider.service('store')._persistService(Service, _this2._name);
73 | }
74 | }).then(function () {
75 | return _this2.value;
76 | });
77 | return this._loading;
78 | }
79 | }]);
80 |
81 | return Loader;
82 | }();
83 |
84 | var _default = Loader;
85 | exports["default"] = _default;
--------------------------------------------------------------------------------
/sample/basic/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
2 |
3 | ## Available Scripts
4 |
5 | In the project directory, you can run:
6 |
7 | ### `npm start`
8 |
9 | Runs the app in the development mode.
10 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
11 |
12 | The page will reload if you make edits.
13 | You will also see any lint errors in the console.
14 |
15 | ### `npm test`
16 |
17 | Launches the test runner in the interactive watch mode.
18 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
19 |
20 | ### `npm run build`
21 |
22 | Builds the app for production to the `build` folder.
23 | It correctly bundles React in production mode and optimizes the build for the best performance.
24 |
25 | The build is minified and the filenames include the hashes.
26 | Your app is ready to be deployed!
27 |
28 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
29 |
30 | ### `npm run eject`
31 |
32 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
33 |
34 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
35 |
36 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
37 |
38 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
39 |
40 | ## Learn More
41 |
42 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
43 |
44 | To learn React, check out the [React documentation](https://reactjs.org/).
45 |
46 | ### Code Splitting
47 |
48 | This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
49 |
50 | ### Analyzing the Bundle Size
51 |
52 | This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
53 |
54 | ### Making a Progressive Web App
55 |
56 | This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
57 |
58 | ### Advanced Configuration
59 |
60 | This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
61 |
62 | ### Deployment
63 |
64 | This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
65 |
66 | ### `npm run build` fails to minify
67 |
68 | This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
69 |
--------------------------------------------------------------------------------
/sample/basic/src/reser/provider.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _jservice = require("jservice");
9 |
10 | var _loader = _interopRequireDefault(require("./loader"));
11 |
12 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
13 |
14 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
15 |
16 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
17 |
18 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
19 |
20 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
21 |
22 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
23 |
24 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
25 |
26 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
27 |
28 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
29 |
30 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
31 |
32 | var ServiceProvider =
33 | /*#__PURE__*/
34 | function (_BaseServiceProvider) {
35 | _inherits(ServiceProvider, _BaseServiceProvider);
36 |
37 | function ServiceProvider() {
38 | _classCallCheck(this, ServiceProvider);
39 |
40 | return _possibleConstructorReturn(this, _getPrototypeOf(ServiceProvider).apply(this, arguments));
41 | }
42 |
43 | _createClass(ServiceProvider, [{
44 | key: "createServices",
45 | value: function createServices(serviceNames, callback) {
46 | var services = {};
47 |
48 | for (var i = 0; i < serviceNames.length; i++) {
49 | var name = serviceNames[i];
50 | var service = this.service(name);
51 |
52 | if (service instanceof _loader["default"]) {
53 | if (!service.value) service.load(true).then(function (isNew) {
54 | return isNew && callback();
55 | });
56 | service = service.value;
57 | }
58 |
59 | services[name] = service;
60 | }
61 |
62 | return services;
63 | }
64 | }]);
65 |
66 | return ServiceProvider;
67 | }(_jservice.ServiceProvider);
68 |
69 | var _default = ServiceProvider;
70 | exports["default"] = _default;
--------------------------------------------------------------------------------
/sample/basic/src/reser/services/util/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var proto = _interopRequireWildcard(require("./proto"));
9 |
10 | function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj["default"] = obj; return newObj; } }
11 |
12 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
13 |
14 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
15 |
16 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
17 |
18 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
19 |
20 | var Util =
21 | /*#__PURE__*/
22 | function () {
23 | function Util(provider, custom) {
24 | _classCallCheck(this, Util);
25 |
26 | _defineProperty(this, "_core", null);
27 |
28 | this._core = provider.get('__core__');
29 |
30 | for (var key in custom) {
31 | if (this[key]) {
32 | // eslint-disable-next-line no-console
33 | console.warn("Built-in util can't add existing \"".concat(key, "\""));
34 | continue;
35 | }
36 |
37 | this[key] = custom[key];
38 | }
39 | }
40 |
41 | _createClass(Util, [{
42 | key: "getStatics",
43 | value: function getStatics(prop) {
44 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
45 | var _this$_core$collectio = this._core.collection,
46 | names = _this$_core$collectio.names,
47 | services = _this$_core$collectio.services;
48 | var result = {};
49 | names = this.invert(names);
50 |
51 | for (var i = 0; i < services.length; i++) {
52 | var service = services[i];
53 |
54 | if (service.async) {
55 | service = service()._service;
56 | if (!service) continue;
57 | }
58 |
59 | var value = service[prop];
60 | if (value === undefined) continue;
61 |
62 | if (options.merge) {
63 | Object.assign(result, value);
64 | continue;
65 | }
66 |
67 | result[names[i]] = value;
68 | }
69 |
70 | return result;
71 | }
72 | }, {
73 | key: "loadAsyncServices",
74 | value: function loadAsyncServices() {
75 | var _this$_core$collectio2 = this._core.collection,
76 | names = _this$_core$collectio2.names,
77 | services = _this$_core$collectio2.services;
78 | var toLoad = [];
79 | names = this.invert(names);
80 |
81 | for (var _len = arguments.length, serviceNames = new Array(_len), _key = 0; _key < _len; _key++) {
82 | serviceNames[_key] = arguments[_key];
83 | }
84 |
85 | for (var i = 0; i < services.length; i++) {
86 | var service = services[i];
87 | if (!service.async) continue;
88 | service = service();
89 | if (serviceNames.length && serviceNames.indexOf(names[i]) === -1) continue;
90 | toLoad.push(service.load());
91 | }
92 |
93 | return Promise.all(toLoad);
94 | }
95 | }]);
96 |
97 | return Util;
98 | }();
99 |
100 | _defineProperty(Util, "service", 'util');
101 |
102 | Object.assign(Util.prototype, proto);
103 | var _default = Util;
104 | exports["default"] = _default;
--------------------------------------------------------------------------------
/src/services/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, combineReducers } from 'redux'
2 |
3 | export default class Store {
4 | static service = 'store'
5 |
6 | base = null
7 | _namespace = 'rsrx:'
8 | _collection = null
9 | _config = null
10 | _reducers = null
11 | _storage = null
12 | _util = null
13 |
14 | constructor(provider, config) {
15 | this._config = config
16 | this._storage = provider.service('storage')
17 | this._util = provider.service('util')
18 | this._collection = provider.service('__core__').collection
19 | const { names, services } = this._collection
20 | let reducers = {}
21 | for (const name in names) {
22 | if (names.hasOwnProperty(name)) {
23 | const service = services[names[name]]
24 | if (service.reducer)
25 | reducers[name] = service.reducer
26 | }
27 | }
28 | this._reducers = reducers
29 | }
30 |
31 | _defaultConfig(arg) {
32 | return {
33 | store: createStore(
34 | arg.rootReducer || (s => s),
35 | arg.initialState || {}
36 | )
37 | }
38 | }
39 |
40 | _getInitialState() {
41 | let state = {}
42 | let storage = this._storage
43 | if (storage) {
44 | return storage.getAllKeys()
45 | .then(keys => {
46 | const toGet = []
47 | keys.sort((a, b) => a.length - b.length)
48 | keys.forEach(key => {
49 | if (!key.startsWith(this._namespace)) return
50 | const path = key.split(':')[1]
51 | // Async service should not be added in initial state
52 | toGet.push(
53 | storage.getItem(key).then(value => {
54 | this._util.set(state, path, JSON.parse(value))
55 | })
56 | )
57 | })
58 | return Promise.all(toGet)
59 | })
60 | .then(() => state)
61 | }
62 | return Promise.resolve({})
63 | }
64 |
65 | _createStore() {
66 | return Promise.resolve()
67 | .then(() => this._getInitialState())
68 | .then(state => {
69 | let arg = {
70 | rootReducer: combineReducers(this._reducers),
71 | initialState: state
72 | }
73 | let result = (
74 | typeof this._config === 'function' ?
75 | this._config(arg) :
76 | this._defaultConfig(arg)
77 | )
78 | this._setStore(result.store)
79 | })
80 | }
81 |
82 | _setStore(store) {
83 | this.base = store
84 | const { names, services } = this._collection
85 | for (const name in names) {
86 | if (!names.hasOwnProperty(name)) continue
87 | const service = services[names[name]]
88 | if (service.persist) {
89 | this._persistService(service, name)
90 | }
91 | }
92 | }
93 |
94 | _persistService(service, name) {
95 | if (service.persist === undefined) return
96 | if (!name) throw new Error('Persistent service must have name')
97 | // Write to storage
98 | if (service.persist === true) {
99 | // Persist whole service
100 | this._persistState(name)
101 | } else {
102 | // Should be array, persist by keys
103 | service.persist.forEach(path => {
104 | this._persistState(name + '.' + path)
105 | })
106 | }
107 | // Mutate reducer
108 | this.base.replaceReducer(
109 | combineReducers({
110 | ...this._reducers,
111 | [name]: service.reducer
112 | })
113 | )
114 | }
115 |
116 | _persistState(path) {
117 | let lastState
118 | let storage = this._storage
119 | let { get } = this._util
120 | let handleChange = () => {
121 | let currentState = get(this.base.getState(), path)
122 | if (currentState !== lastState) {
123 | lastState = currentState
124 | // Write to storage
125 | if (storage && path)
126 | storage.setItem(this._namespace + path, JSON.stringify(currentState))
127 | }
128 | }
129 | let unsubscribe = this.base.subscribe(handleChange)
130 | handleChange()
131 | return unsubscribe
132 | }
133 |
134 | getStore() {
135 | return this.base
136 | }
137 |
138 | getState() {
139 | return this.base.getState()
140 | }
141 |
142 | dispatch(action) {
143 | return this.base.dispatch(action)
144 | }
145 |
146 | replaceReducer(nextReducer) {
147 | return this.base.replaceReducer(nextReducer)
148 | }
149 |
150 | subscribe(listener) {
151 | return this.base.subscribe(listener)
152 | }
153 |
154 | static start(provider) {
155 | const store = provider.service('store')
156 | return store._createStore()
157 | }
158 |
159 | }
160 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Reser
2 |
3 | An asynchronous DI framework for modular React and React-Native using [JService](https://github.com/rhaldkhein/jservice).
4 |
5 | **Key Features**
6 |
7 | - Dependency Injection
8 | - Asynchronous Service
9 | - Code Splitting
10 | - Persistent `redux` State
11 | - Abstraction for `react-redux`
12 | - For `react` and `react-native`
13 |
14 | ## Install
15 |
16 | ```sh
17 | npm install reser
18 | ```
19 |
20 | ## Basic Usage
21 |
22 | Create project with `create-react-app` and register all your services in registry file.
23 |
24 | In file `registry.js`, you can add or configure services. There are also some built-in services you can configure like `store` (derived from redux), `storage` (derived from localstorage), etc.
25 |
26 | ```javascript
27 | import UserService from './services/user.js'
28 | // ...
29 |
30 | export default function (services) {
31 |
32 | services.add(UserService)
33 | services.add(BookingService)
34 |
35 | // Services with components
36 | services.add(MapService)
37 | services.add(SocialService)
38 |
39 | // Async services
40 | services.add(() => import('./services/myAsync'), 'async')
41 |
42 | // ...
43 | }
44 | ```
45 |
46 | In file `App.js`, you have to wrap it with `withContainer` and pass registry and root component.
47 |
48 | ```javascript
49 | import React from 'react'
50 | import { withContainer } from 'reser'
51 | import registry from './registry.js'
52 | // ...
53 |
54 | import Home from './routes/Home'
55 | // ...
56 |
57 | function App({ container }) {
58 | return (
59 | container.isReady &&
60 |
61 |
62 |
63 |
64 | - Home
65 | - About
66 | - Users
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | )
77 | }
78 |
79 | // Inject root component with DI container
80 | export default withContainer(registry)(App)
81 | ```
82 |
83 | Services are the basic building blocks for dependency injection. And here's the file `services/user.js` that depends on `store` service to get the state or dispatch an action.
84 |
85 | ```javascript
86 | const initial = {
87 | id: null,
88 | name: 'Unknown',
89 | age: 0
90 | }
91 |
92 | function reducer(state = initial, action) {
93 | // ...
94 | }
95 |
96 | class UserService {
97 | static service = 'user'
98 | static reducer = reducer
99 | static persist = true // Save state to local storage
100 |
101 | constructor(provider) {
102 | // Let's inject the built-in store service
103 | this.store = provider.service('store')
104 | // And http service to handle REST request
105 | this.http = provider.service('http')
106 | }
107 |
108 | getCurrentUser() {
109 | return this.store.getState().user.current
110 | }
111 |
112 | signIn(email, password) {
113 | return this.http.post('/signin', { email, password })
114 | .then(user => {
115 | return this.store.dispatch({
116 | type: 'SET_CURRENT_USER',
117 | user
118 | })
119 | })
120 | }
121 |
122 | // ...
123 | }
124 | ```
125 |
126 | Then route component `routes/Home.js` that depends on `user` service. To inject service in component, use `withService`.
127 |
128 | ```javascript
129 | import React from 'react'
130 | import { withService, andState } from 'reser'
131 |
132 | class Home {
133 |
134 | state = { email: null, password: null }
135 |
136 | constructor(props) {
137 | super(props)
138 | // Create a reference to user service
139 | this.userService = props.services.user
140 | }
141 |
142 | signIn = e => {
143 | e.preventDefault()
144 | // If the sign-in succedded, component will update automatically
145 | this.userService.signIn(this.state.email, this.state.password)
146 | }
147 |
148 | render() {
149 | const currentUser = this.props.state.user
150 | // It will display `Hello, Foo!` or sign-in form
151 | return (
152 |
163 | )
164 | }
165 | }
166 |
167 | // Here we inject `user` service and its state.
168 | // Remove andState, if you only need the service.
169 | export default withService('user', andState())(Home)
170 | ```
171 |
172 | ## License
173 |
174 | MIT
175 |
176 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { connect, Provider } from 'react-redux'
3 | import BaseBuilder from 'jservice'
4 | import ServiceCollection from './collection'
5 | import ServiceProvider from './provider'
6 |
7 | // Built-in services
8 | import UtilService from './services/util'
9 | import StorageService from './services/storage'
10 | import StoreService from './services/store'
11 |
12 | // DI container contexts
13 | const ContainerContext = React.createContext()
14 | const AsyncCountContext = React.createContext(0)
15 |
16 | class ReactServices extends BaseBuilder {
17 |
18 | constructor() {
19 | super()
20 | this.collection = new ServiceCollection(this)
21 | this.provider = new ServiceProvider(this.collection)
22 | }
23 |
24 | createProvider() {
25 | throw new Error('Scoped providers are not supported yet')
26 | }
27 |
28 | }
29 |
30 | function createContainer() {
31 | return new ReactServices()
32 | .build(services => {
33 | services.add(UtilService)
34 | services.add(StorageService)
35 | services.add(StoreService)
36 | })
37 | }
38 |
39 | function mapStateToProps(serviceNames, state) {
40 | return serviceNames.reduce((result, name) => {
41 | const val = state[name]
42 | if (val) result[name] = val
43 | return result
44 | }, {})
45 | }
46 |
47 | export function withContainer(registry) {
48 | return function (ChildComponent) {
49 | return class extends React.Component {
50 | constructor(props) {
51 | super(props)
52 | this.state = { count: 0 }
53 | this.container = createContainer().build(registry)
54 | this.container.start().then(() => this.forceUpdate())
55 | this.contextValue = {
56 | container: this.container,
57 | asyncLoaded: () => {
58 | this.setState({ count: this.state.count + 1 })
59 | }
60 | }
61 | }
62 | render() {
63 | const store = this.container.provider.service('store').getStore()
64 | const childElement = React.createElement(ChildComponent, {
65 | container: this.container,
66 | ...this.props
67 | })
68 | return React.createElement(
69 | ContainerContext.Provider,
70 | { value: this.contextValue },
71 | React.createElement(
72 | AsyncCountContext.Provider,
73 | { value: this.state.count },
74 | !store ? childElement :
75 | React.createElement(
76 | Provider,
77 | { store },
78 | childElement
79 | ))
80 | )
81 | }
82 | }
83 | }
84 | }
85 |
86 | export function withNewService(registry) {
87 | return function (ChildComponent) {
88 | return function (props) {
89 | return React.createElement(ContainerContext.Consumer, null,
90 | function (context) {
91 | context.container.build(registry)
92 | return React.createElement(
93 | ChildComponent,
94 | { container: context.container },
95 | props.children
96 | )
97 | }
98 | )
99 | }
100 | }
101 | }
102 |
103 | export function withService(...serviceNames) {
104 | return function (ChildComponent) {
105 | if (Array.isArray(serviceNames[serviceNames.length - 1])) {
106 | const states = serviceNames.pop()
107 | ChildComponent = withState.apply(null,
108 | states.length ? states : serviceNames)(ChildComponent)
109 | }
110 | return function (props) {
111 | return React.createElement(ContainerContext.Consumer, null,
112 | function (context) {
113 | return React.createElement(AsyncCountContext.Consumer, null,
114 | function () {
115 | return React.createElement(ChildComponent, {
116 | services: context.container.provider.createServices(
117 | serviceNames,
118 | context.asyncLoaded
119 | ),
120 | ...props
121 | })
122 | }
123 | )
124 | }
125 | )
126 | }
127 | }
128 | }
129 |
130 | export function withState(...serviceNames) {
131 | return function (ChildComponent) {
132 | if (Array.isArray(serviceNames[serviceNames.length - 1])) {
133 | const services = serviceNames.pop()
134 | ChildComponent = withService.apply(null,
135 | services.length ? services : serviceNames)(ChildComponent)
136 | }
137 | return connect(
138 | state => ({
139 | state: typeof serviceNames[0] === 'function' ?
140 | serviceNames[0](state) :
141 | mapStateToProps(serviceNames, state)
142 | })
143 | )(ChildComponent)
144 | }
145 | }
146 |
147 | export function and(...serviceNames) {
148 | return Array.isArray(serviceNames[0]) ? serviceNames[0] : serviceNames
149 | }
150 |
151 | export {
152 | // HOC
153 | createContainer,
154 | and as andService,
155 | and as andState,
156 | // Services
157 | StorageService,
158 | StoreService,
159 | UtilService
160 | }
161 |
--------------------------------------------------------------------------------
/sample/basic/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read https://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit https://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/sample/basic/src/reser/collection.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _jservice = require("jservice");
9 |
10 | var _proto = require("./services/util/proto");
11 |
12 | var _loader = _interopRequireDefault(require("./loader"));
13 |
14 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
15 |
16 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
17 |
18 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
19 |
20 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
21 |
22 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
23 |
24 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
25 |
26 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
27 |
28 | function _get(target, property, receiver) { if (typeof Reflect !== "undefined" && Reflect.get) { _get = Reflect.get; } else { _get = function _get(target, property, receiver) { var base = _superPropBase(target, property); if (!base) return; var desc = Object.getOwnPropertyDescriptor(base, property); if (desc.get) { return desc.get.call(receiver); } return desc.value; }; } return _get(target, property, receiver || target); }
29 |
30 | function _superPropBase(object, property) { while (!Object.prototype.hasOwnProperty.call(object, property)) { object = _getPrototypeOf(object); if (object === null) break; } return object; }
31 |
32 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
33 |
34 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
35 |
36 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
37 |
38 | var ServiceCollection =
39 | /*#__PURE__*/
40 | function (_BaseServiceCollectio) {
41 | _inherits(ServiceCollection, _BaseServiceCollectio);
42 |
43 | function ServiceCollection(core) {
44 | var _this;
45 |
46 | _classCallCheck(this, ServiceCollection);
47 |
48 | _this = _possibleConstructorReturn(this, _getPrototypeOf(ServiceCollection).call(this, core));
49 | _this._core = core;
50 | return _this;
51 | }
52 |
53 | _createClass(ServiceCollection, [{
54 | key: "_validate",
55 | value: function _validate(service) {
56 | var err = null;
57 | if (service.async) err = 'async'; // Add more static validation
58 |
59 | if (err) throw new Error("Invalid service signature \"".concat(err, "\""));
60 | }
61 | }, {
62 | key: "_push",
63 | value: function _push(Service, name, config, skip) {
64 | this._validate(Service); // Check if service is async or not
65 |
66 |
67 | if ((0, _proto.isFunction)(Service) && !(0, _proto.isConstructor)(Service)) {
68 | // Async service
69 | Service.service = name;
70 | var loader = new _loader["default"](this._core.provider, Service, name, config);
71 |
72 | var LoaderService = function LoaderService() {
73 | return loader;
74 | };
75 |
76 | LoaderService.type = Service.type;
77 | LoaderService.service = name;
78 | LoaderService.async = true;
79 | Service = LoaderService;
80 | }
81 |
82 | _get(_getPrototypeOf(ServiceCollection.prototype), "_push", this).call(this, Service, name, config, skip);
83 | }
84 | }, {
85 | key: "isAsyncService",
86 | value: function isAsyncService(name) {
87 | var names = this.names,
88 | services = this.services;
89 |
90 | for (var key in names) {
91 | if (names.hasOwnProperty(key) && key === name) {
92 | return services[names[key]].async || false;
93 | }
94 | }
95 |
96 | return true;
97 | }
98 | }, {
99 | key: "scoped",
100 | value: function scoped() {
101 | throw new Error('Scoped services are not supported yet');
102 | }
103 | }, {
104 | key: "transient",
105 | value: function transient() {
106 | throw new Error('Transient services are not supported yet');
107 | }
108 | }]);
109 |
110 | return ServiceCollection;
111 | }(_jservice.ServiceCollection);
112 |
113 | var _default = ServiceCollection;
114 | exports["default"] = _default;
--------------------------------------------------------------------------------
/sample/basic/src/reser/services/store.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports["default"] = void 0;
7 |
8 | var _redux = require("redux");
9 |
10 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; }
11 |
12 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
13 |
14 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
15 |
16 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
17 |
18 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
19 |
20 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
21 |
22 | var Store =
23 | /*#__PURE__*/
24 | function () {
25 | function Store(provider, config) {
26 | _classCallCheck(this, Store);
27 |
28 | _defineProperty(this, "base", null);
29 |
30 | _defineProperty(this, "_namespace", 'rsrx:');
31 |
32 | _defineProperty(this, "_collection", null);
33 |
34 | _defineProperty(this, "_config", null);
35 |
36 | _defineProperty(this, "_reducers", null);
37 |
38 | _defineProperty(this, "_storage", null);
39 |
40 | _defineProperty(this, "_util", null);
41 |
42 | this._config = config;
43 | this._storage = provider.service('storage');
44 | this._util = provider.service('util');
45 | this._collection = provider.service('__core__').collection;
46 | var _this$_collection = this._collection,
47 | names = _this$_collection.names,
48 | services = _this$_collection.services;
49 | var reducers = {};
50 |
51 | for (var name in names) {
52 | if (names.hasOwnProperty(name)) {
53 | var service = services[names[name]];
54 | if (service.reducer) reducers[name] = service.reducer;
55 | }
56 | }
57 |
58 | this._reducers = reducers;
59 | }
60 |
61 | _createClass(Store, [{
62 | key: "_defaultConfig",
63 | value: function _defaultConfig(arg) {
64 | return {
65 | store: (0, _redux.createStore)(arg.rootReducer || function (s) {
66 | return s;
67 | }, arg.initialState || {})
68 | };
69 | }
70 | }, {
71 | key: "_getInitialState",
72 | value: function _getInitialState() {
73 | var _this = this;
74 |
75 | var state = {};
76 | var storage = this._storage;
77 |
78 | if (storage) {
79 | return storage.getAllKeys().then(function (keys) {
80 | var toGet = [];
81 | keys.sort(function (a, b) {
82 | return a.length - b.length;
83 | });
84 | keys.forEach(function (key) {
85 | if (!key.startsWith(_this._namespace)) return;
86 | var path = key.split(':')[1]; // Async service should not be added in initial state
87 |
88 | toGet.push(storage.getItem(key).then(function (value) {
89 | _this._util.set(state, path, JSON.parse(value));
90 | }));
91 | });
92 | return Promise.all(toGet);
93 | }).then(function () {
94 | return state;
95 | });
96 | }
97 |
98 | return Promise.resolve({});
99 | }
100 | }, {
101 | key: "_createStore",
102 | value: function _createStore() {
103 | var _this2 = this;
104 |
105 | return Promise.resolve().then(function () {
106 | return _this2._getInitialState();
107 | }).then(function (state) {
108 | var arg = {
109 | rootReducer: (0, _redux.combineReducers)(_this2._reducers),
110 | initialState: state
111 | };
112 | var result = typeof _this2._config === 'function' ? _this2._config(arg) : _this2._defaultConfig(arg);
113 |
114 | _this2._setStore(result.store);
115 | });
116 | }
117 | }, {
118 | key: "_setStore",
119 | value: function _setStore(store) {
120 | this.base = store;
121 | var _this$_collection2 = this._collection,
122 | names = _this$_collection2.names,
123 | services = _this$_collection2.services;
124 |
125 | for (var name in names) {
126 | if (!names.hasOwnProperty(name)) continue;
127 | var service = services[names[name]];
128 |
129 | if (service.persist) {
130 | this._persistService(service, name);
131 | }
132 | }
133 | }
134 | }, {
135 | key: "_persistService",
136 | value: function _persistService(service, name) {
137 | var _this3 = this;
138 |
139 | if (service.persist === undefined) return;
140 | if (!name) throw new Error('Persistent service must have name'); // Write to storage
141 |
142 | if (service.persist === true) {
143 | // Persist whole service
144 | this._persistState(name);
145 | } else {
146 | // Should be array, persist by keys
147 | service.persist.forEach(function (path) {
148 | _this3._persistState(name + '.' + path);
149 | });
150 | } // Mutate reducer
151 |
152 |
153 | this.base.replaceReducer((0, _redux.combineReducers)(_objectSpread({}, this._reducers, _defineProperty({}, name, service.reducer))));
154 | }
155 | }, {
156 | key: "_persistState",
157 | value: function _persistState(path) {
158 | var _this4 = this;
159 |
160 | var lastState;
161 | var storage = this._storage;
162 | var get = this._util.get;
163 |
164 | var handleChange = function handleChange() {
165 | var currentState = get(_this4.base.getState(), path);
166 |
167 | if (currentState !== lastState) {
168 | lastState = currentState; // Write to storage
169 |
170 | if (storage && path) storage.setItem(_this4._namespace + path, JSON.stringify(currentState));
171 | }
172 | };
173 |
174 | var unsubscribe = this.base.subscribe(handleChange);
175 | handleChange();
176 | return unsubscribe;
177 | }
178 | }, {
179 | key: "getStore",
180 | value: function getStore() {
181 | return this.base;
182 | }
183 | }, {
184 | key: "getState",
185 | value: function getState() {
186 | return this.base.getState();
187 | }
188 | }, {
189 | key: "dispatch",
190 | value: function dispatch(action) {
191 | return this.base.dispatch(action);
192 | }
193 | }, {
194 | key: "replaceReducer",
195 | value: function replaceReducer(nextReducer) {
196 | return this.base.replaceReducer(nextReducer);
197 | }
198 | }, {
199 | key: "subscribe",
200 | value: function subscribe(listener) {
201 | return this.base.subscribe(listener);
202 | }
203 | }], [{
204 | key: "start",
205 | value: function start(provider) {
206 | var store = provider.service('store');
207 | return store._createStore();
208 | }
209 | }]);
210 |
211 | return Store;
212 | }();
213 |
214 | exports["default"] = Store;
215 |
216 | _defineProperty(Store, "service", 'store');
--------------------------------------------------------------------------------
/sample/basic/src/reser/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | Object.defineProperty(exports, "__esModule", {
4 | value: true
5 | });
6 | exports.withContainer = withContainer;
7 | exports.withNewService = withNewService;
8 | exports.withService = withService;
9 | exports.withState = withState;
10 | exports.andState = exports.andService = exports.and = and;
11 | exports.createContainer = createContainer;
12 | Object.defineProperty(exports, "UtilService", {
13 | enumerable: true,
14 | get: function get() {
15 | return _util["default"];
16 | }
17 | });
18 | Object.defineProperty(exports, "StorageService", {
19 | enumerable: true,
20 | get: function get() {
21 | return _storage["default"];
22 | }
23 | });
24 | Object.defineProperty(exports, "StoreService", {
25 | enumerable: true,
26 | get: function get() {
27 | return _store["default"];
28 | }
29 | });
30 |
31 | var _react = _interopRequireDefault(require("react"));
32 |
33 | var _reactRedux = require("react-redux");
34 |
35 | var _jservice = _interopRequireDefault(require("jservice"));
36 |
37 | var _collection = _interopRequireDefault(require("./collection"));
38 |
39 | var _provider = _interopRequireDefault(require("./provider"));
40 |
41 | var _util = _interopRequireDefault(require("./services/util"));
42 |
43 | var _storage = _interopRequireDefault(require("./services/storage"));
44 |
45 | var _store = _interopRequireDefault(require("./services/store"));
46 |
47 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
48 |
49 | function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { keys.push.apply(keys, Object.getOwnPropertySymbols(object)); } if (enumerableOnly) keys = keys.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); return keys; }
50 |
51 | function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
52 |
53 | function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
54 |
55 | function _typeof(obj) { if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { _typeof = function _typeof(obj) { return typeof obj; }; } else { _typeof = function _typeof(obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; } return _typeof(obj); }
56 |
57 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
58 |
59 | function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }
60 |
61 | function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }
62 |
63 | function _possibleConstructorReturn(self, call) { if (call && (_typeof(call) === "object" || typeof call === "function")) { return call; } return _assertThisInitialized(self); }
64 |
65 | function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) { return o.__proto__ || Object.getPrototypeOf(o); }; return _getPrototypeOf(o); }
66 |
67 | function _assertThisInitialized(self) { if (self === void 0) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return self; }
68 |
69 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); if (superClass) _setPrototypeOf(subClass, superClass); }
70 |
71 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); }
72 |
73 | // DI container contexts
74 | var ContainerContext = _react["default"].createContext();
75 |
76 | var AsyncCountContext = _react["default"].createContext(0);
77 |
78 | var ReactServices =
79 | /*#__PURE__*/
80 | function (_BaseBuilder) {
81 | _inherits(ReactServices, _BaseBuilder);
82 |
83 | function ReactServices() {
84 | var _this;
85 |
86 | _classCallCheck(this, ReactServices);
87 |
88 | _this = _possibleConstructorReturn(this, _getPrototypeOf(ReactServices).call(this));
89 | _this.collection = new _collection["default"](_assertThisInitialized(_this));
90 | _this.provider = new _provider["default"](_this.collection);
91 | return _this;
92 | }
93 |
94 | _createClass(ReactServices, [{
95 | key: "createProvider",
96 | value: function createProvider() {
97 | throw new Error('Scoped providers are not supported yet');
98 | }
99 | }]);
100 |
101 | return ReactServices;
102 | }(_jservice["default"]);
103 |
104 | function createContainer() {
105 | return new ReactServices().build(function (services) {
106 | services.add(_util["default"]);
107 | services.add(_storage["default"]);
108 | services.add(_store["default"]);
109 | });
110 | }
111 |
112 | function mapStateToProps(serviceNames, state) {
113 | return serviceNames.reduce(function (result, name) {
114 | var val = state[name];
115 | if (val) result[name] = val;
116 | return result;
117 | }, {});
118 | }
119 |
120 | function withContainer(registry) {
121 | return function (ChildComponent) {
122 | return (
123 | /*#__PURE__*/
124 | function (_React$Component) {
125 | _inherits(_class, _React$Component);
126 |
127 | function _class(props) {
128 | var _this2;
129 |
130 | _classCallCheck(this, _class);
131 |
132 | _this2 = _possibleConstructorReturn(this, _getPrototypeOf(_class).call(this, props));
133 | _this2.state = {
134 | count: 0
135 | };
136 | _this2.container = createContainer().build(registry);
137 |
138 | _this2.container.start().then(function () {
139 | return _this2.forceUpdate();
140 | });
141 |
142 | _this2.contextValue = {
143 | container: _this2.container,
144 | asyncLoaded: function asyncLoaded() {
145 | _this2.setState({
146 | count: _this2.state.count + 1
147 | });
148 | }
149 | };
150 | return _this2;
151 | }
152 |
153 | _createClass(_class, [{
154 | key: "render",
155 | value: function render() {
156 | var store = this.container.provider.service('store').getStore();
157 |
158 | var childElement = _react["default"].createElement(ChildComponent, _objectSpread({
159 | container: this.container
160 | }, this.props));
161 |
162 | return _react["default"].createElement(ContainerContext.Provider, {
163 | value: this.contextValue
164 | }, _react["default"].createElement(AsyncCountContext.Provider, {
165 | value: this.state.count
166 | }, !store ? childElement : _react["default"].createElement(_reactRedux.Provider, {
167 | store: store
168 | }, childElement)));
169 | }
170 | }]);
171 |
172 | return _class;
173 | }(_react["default"].Component)
174 | );
175 | };
176 | }
177 |
178 | function withNewService(registry) {
179 | return function (ChildComponent) {
180 | return function (props) {
181 | return _react["default"].createElement(ContainerContext.Consumer, null, function (context) {
182 | context.container.build(registry);
183 | return _react["default"].createElement(ChildComponent, {
184 | container: context.container
185 | }, props.children);
186 | });
187 | };
188 | };
189 | }
190 |
191 | function withService() {
192 | for (var _len = arguments.length, serviceNames = new Array(_len), _key = 0; _key < _len; _key++) {
193 | serviceNames[_key] = arguments[_key];
194 | }
195 |
196 | return function (ChildComponent) {
197 | if (Array.isArray(serviceNames[serviceNames.length - 1])) {
198 | var states = serviceNames.pop();
199 | ChildComponent = withState.apply(null, states.length ? states : serviceNames)(ChildComponent);
200 | }
201 |
202 | return function (props) {
203 | return _react["default"].createElement(ContainerContext.Consumer, null, function (context) {
204 | return _react["default"].createElement(AsyncCountContext.Consumer, null, function () {
205 | return _react["default"].createElement(ChildComponent, _objectSpread({
206 | services: context.container.provider.createServices(serviceNames, context.asyncLoaded)
207 | }, props));
208 | });
209 | });
210 | };
211 | };
212 | }
213 |
214 | function withState() {
215 | for (var _len2 = arguments.length, serviceNames = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
216 | serviceNames[_key2] = arguments[_key2];
217 | }
218 |
219 | return function (ChildComponent) {
220 | if (Array.isArray(serviceNames[serviceNames.length - 1])) {
221 | var services = serviceNames.pop();
222 | ChildComponent = withService.apply(null, services.length ? services : serviceNames)(ChildComponent);
223 | }
224 |
225 | return (0, _reactRedux.connect)(function (state) {
226 | return {
227 | state: typeof serviceNames[0] === 'function' ? serviceNames[0](state) : mapStateToProps(serviceNames, state)
228 | };
229 | })(ChildComponent);
230 | };
231 | }
232 |
233 | function and() {
234 | for (var _len3 = arguments.length, serviceNames = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
235 | serviceNames[_key3] = arguments[_key3];
236 | }
237 |
238 | return Array.isArray(serviceNames[0]) ? serviceNames[0] : serviceNames;
239 | }
--------------------------------------------------------------------------------