Comparing version 1.11.1 to 2.0.0
@@ -5,2 +5,28 @@ # Change Log | ||
## [2.0.0 - 2018-03-17](https://github.com/alonrbar/redux-app/tree/v2.0.0) | ||
### Added | ||
- Actions are now opt-in and declared via the `action` decorator. | ||
- Dynamic component class name generation - generated components name follows | ||
the following pattern: "OriginalClassName_ReduxAppComponent" (As a consequence | ||
the `emitClassNames` was removed as it is no longer needed). | ||
- Improve overall performance (remove state "finalization" step). | ||
### Changed | ||
- `uppercaseActions` option defaults to false. | ||
- Rename SchemaOptions to ActionOptions | ||
- Don't remove getters from state. | ||
### Removed | ||
- Remove 5 decorators: `component`, `connect`, `computed`, `method` and `noDispatch`. | ||
- Remove `convertToPlainObject` option. | ||
- Remove package-lock.json (using yarn.lock). | ||
### Fixed | ||
- Fix duplicate component creation when created from the same template instance. | ||
## [1.11.1 - 2018-27-2](https://github.com/alonrbar/redux-app/tree/v1.11.1) | ||
@@ -10,12 +36,8 @@ | ||
- The use of `connect` inside the application tree is deprecated and **will be | ||
remove in the next version**. Connection from outside the application tree | ||
will still be supported (do note however that `connect` will move to separate | ||
package in the future). | ||
- The use of `connect` deprecated and **will be remove in the next version**. | ||
There are several reasons for that: it is a source of bugs, it makes other | ||
features harder to implement and it encourages a pseudo dependency injection | ||
anti-pattern (DI is great, the pseudo form that `connect` encourage is bad. If | ||
you want to use DI you should use a proper DI container. As a side note I plan | ||
on publishing my own custom container as a package in the future). | ||
anti-pattern (DI is great, the pseudo form that `connect` encourage isn't - | ||
if you want to use DI you should use a proper DI container). | ||
@@ -22,0 +44,0 @@ ### Fixed |
@@ -8,6 +8,7 @@ import { Store, StoreEnhancer } from 'redux'; | ||
/** | ||
* Class decorator. | ||
* Method decorator. | ||
* | ||
* Mark this method as a Redux action. | ||
*/ | ||
export function component(ctor: Function): any; | ||
export function component(options: SchemaOptions): any; | ||
export function action(target: object, propertyKey: string | symbol): void; | ||
@@ -25,18 +26,4 @@ /** | ||
/** | ||
* Method decorator. | ||
* | ||
* Mark this method as a simple js method (not dispatching an action). | ||
*/ | ||
export function noDispatch(target: any, propertyKey: string | symbol): void; | ||
/** | ||
* Property decorator. | ||
* | ||
* Computed values are computed each time the store state is changed. | ||
*/ | ||
export function computed(target: any, propertyKey: string | symbol): void; | ||
/** | ||
* Property decorator. | ||
*/ | ||
export function withId(target: object, propertyKey: string | symbol): void; | ||
@@ -51,19 +38,2 @@ export function withId(id?: any): PropertyDecorator; | ||
/** | ||
* Property decorator. | ||
* | ||
* Connects the property to a component in the specified (or default) app. | ||
* | ||
* Usage Note: | ||
* | ||
* Connected properties can be thought of as pointers to other components. If | ||
* the connected property has an initial value assigned, it will be overridden | ||
* once the component is connected. One consequence of this fact is that there | ||
* must be at least one source component, i.e. there must be at least one | ||
* component of that type that has a value and is not decorated with the | ||
* 'connect' decorator. | ||
*/ | ||
export function connect(options?: ConnectOptions): PropertyDecorator; | ||
export function connect(target: any, propertyKey: string | symbol): void; | ||
// | ||
@@ -75,6 +45,2 @@ // ReduxApp | ||
// | ||
// static members | ||
// | ||
/** | ||
@@ -99,6 +65,2 @@ * Global redux-app options. | ||
// | ||
// instance members | ||
// | ||
readonly name: string; | ||
@@ -137,5 +99,5 @@ /** | ||
// | ||
export type Method = Function; | ||
export interface Constructor<T> { | ||
@@ -153,3 +115,3 @@ new(...args: any[]): T; | ||
export class SchemaOptions { | ||
export class ActionOptions { | ||
/** | ||
@@ -169,3 +131,3 @@ * Add the class name of the object that holds the action to the action name. | ||
* 'INCREMENT_COUNTER'. | ||
* Default value: true. | ||
* Default value: false. | ||
*/ | ||
@@ -175,34 +137,2 @@ uppercaseActions?: boolean; | ||
export class ConnectOptions { | ||
/** | ||
* The name of the ReduxApp instance to connect to. | ||
* If not specified will connect to default app. | ||
*/ | ||
app?: string; | ||
/** | ||
* The ID of the target component (assuming the ID was assigned to the | ||
* component by the 'withId' decorator). | ||
* If not specified will connect to the first available component of that type. | ||
*/ | ||
id?: any; | ||
/** | ||
* The 'connect' decorator uses a getter to connect to the it's target. By | ||
* default the getter is replaced with a standard value (reference) once the | ||
* first non-empty value is retrieved. Set this value to true to leave the | ||
* getter in place. | ||
* Default value: false | ||
*/ | ||
live?: boolean; | ||
} | ||
export class ComputedOptions { | ||
/** | ||
* Whether to perform deep comparison or a simple equality comparison | ||
* before updating computed values. Using deep comparison has a small | ||
* additional performance cost. | ||
* Default value: true. | ||
*/ | ||
deepComparison: boolean; | ||
} | ||
export class AppOptions { | ||
@@ -229,47 +159,5 @@ /** | ||
/** | ||
* When set to 'true' every component will have an additional __originalClassName__ property. | ||
* May be useful for debugging. | ||
* Default value: false. | ||
* Customize actions naming. | ||
*/ | ||
emitClassNames: boolean; | ||
/** | ||
* #### From the original redux FAQ: | ||
* | ||
* Q: Can I put functions, promises, or other non-serializable items in my | ||
* store state? | ||
* | ||
* A: It is highly recommended that you only put plain serializable objects, | ||
* arrays, and primitives into your store. It's technically possible to | ||
* insert non-serializable items into the store, but doing so can break the | ||
* ability to persist and rehydrate the contents of a store, as well as | ||
* interfere with time-travel debugging. | ||
* | ||
* If you are okay with things like persistence and time-travel debugging | ||
* potentially not working as intended, then you are totally welcome to put | ||
* non-serializable items into your Redux store. Ultimately, it's your | ||
* application, and how you implement it is up to you. As with many other | ||
* things about Redux, just be sure you understand what tradeoffs are | ||
* involved. | ||
* | ||
* #### The case in redux-app: | ||
* | ||
* By default redux-app aligns with redux recommendations and treats | ||
* everything stored in the store state as a plain object to prevent the | ||
* previously described issues. This approach may come with some performance | ||
* (and of course usability) cost. Therefor if you don't care about time | ||
* travel debugging or rehydration of the store content etc. and you don't | ||
* want to pay the aforementioned cost you can set this option to false. | ||
* | ||
* Default value: true. | ||
*/ | ||
convertToPlainObject?: boolean; | ||
/** | ||
* Global defaults. | ||
* Options supplied explicitly via the decorator will override options specified here. | ||
*/ | ||
schema: SchemaOptions; | ||
/** | ||
* Customize `computed` properties behavior. | ||
*/ | ||
computed: ComputedOptions; | ||
action: ActionOptions; | ||
} | ||
@@ -276,0 +164,0 @@ |
@@ -104,3 +104,3 @@ (function webpackUniversalModuleDefinition(root, factory) { | ||
var COMPONENT_INFO = Symbol('REDUX-APP.COMPONENT_INFO'); | ||
var CREATOR_INFO = Symbol('REDUX-APP.CREATOR_INFO'); | ||
var COMPONENT_TEMPLATE_INFO = Symbol('REDUX-APP.COMPONENT_TEMPLATE_INFO'); | ||
var CLASS_INFO = Symbol('REDUX-APP.CLASS_INFO'); | ||
@@ -113,4 +113,2 @@ var AUTO_ID = Symbol('REDUX-APP.AUTO_ID'); | ||
function ClassInfo() { | ||
this.computedGetters = {}; | ||
this.connectedProps = {}; | ||
this.ignoreState = {}; | ||
@@ -137,3 +135,6 @@ } | ||
var componentInfo_ComponentInfo = (function () { | ||
function ComponentInfo() { | ||
function ComponentInfo(template, dispatch, id) { | ||
this.originalClass = template.constructor; | ||
this.dispatch = dispatch; | ||
this.id = id; | ||
} | ||
@@ -145,4 +146,5 @@ ComponentInfo.getInfo = function (component) { | ||
}; | ||
ComponentInfo.initInfo = function (component) { | ||
return setSymbol(component, COMPONENT_INFO, new ComponentInfo()); | ||
ComponentInfo.initInfo = function (component, template, dispatch, id) { | ||
var info = new ComponentInfo(template, dispatch, id); | ||
return setSymbol(component, COMPONENT_INFO, info); | ||
}; | ||
@@ -153,47 +155,12 @@ return ComponentInfo; | ||
// CONCATENATED MODULE: ./src/utils/defineProperty.ts | ||
var dataDescriptor = { | ||
writable: true, | ||
configurable: true, | ||
enumerable: true | ||
}; | ||
var accessorDescriptor = { | ||
configurable: true, | ||
enumerable: true | ||
}; | ||
function deferredDefineProperty(target, propertyKey, descriptor) { | ||
var init = function (isGet) { return function (newVal) { | ||
Object.defineProperty(this, propertyKey, descriptor); | ||
if (isGet) { | ||
return this[propertyKey]; | ||
} | ||
else { | ||
this[propertyKey] = newVal; | ||
} | ||
}; }; | ||
return Object.defineProperty(target, propertyKey, { | ||
get: init(true), | ||
set: init(false), | ||
enumerable: true, | ||
configurable: true | ||
}); | ||
} | ||
// CONCATENATED MODULE: ./src/options.ts | ||
var SchemaOptions = (function () { | ||
function SchemaOptions() { | ||
var ActionOptions = (function () { | ||
function ActionOptions() { | ||
this.actionNamespace = true; | ||
this.actionNamespaceSeparator = '.'; | ||
this.uppercaseActions = true; | ||
this.uppercaseActions = false; | ||
} | ||
return SchemaOptions; | ||
return ActionOptions; | ||
}()); | ||
var ComputedOptions = (function () { | ||
function ComputedOptions() { | ||
this.deepComparison = true; | ||
} | ||
return ComputedOptions; | ||
}()); | ||
var AppOptions = (function () { | ||
@@ -217,6 +184,3 @@ function AppOptions() { | ||
this.logLevel = LogLevel.Warn; | ||
this.emitClassNames = false; | ||
this.convertToPlainObject = true; | ||
this.schema = new SchemaOptions(); | ||
this.computed = new ComputedOptions(); | ||
this.action = new ActionOptions(); | ||
} | ||
@@ -292,38 +256,69 @@ return GlobalOptions; | ||
function isPrimitive(val) { | ||
if (!val) | ||
return true; | ||
var type = typeof val; | ||
return type !== 'object' && type !== 'function'; | ||
function clearProperties(obj) { | ||
var keys = Object.keys(obj); | ||
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { | ||
var key = keys_1[_i]; | ||
delete obj[key]; | ||
} | ||
} | ||
function getMethods(obj, bind) { | ||
if (bind === void 0) { bind = false; } | ||
if (!obj) | ||
return undefined; | ||
var proto; | ||
if (typeof obj === 'object') { | ||
proto = Object.getPrototypeOf(obj); | ||
var DescriptorType; | ||
(function (DescriptorType) { | ||
DescriptorType["None"] = "None"; | ||
DescriptorType["Field"] = "Field"; | ||
DescriptorType["Property"] = "Property"; | ||
DescriptorType["Method"] = "Method"; | ||
})(DescriptorType || (DescriptorType = {})); | ||
function defineProperties(target, source, descriptorTypes) { | ||
var descriptors = getAllPropertyDescriptors(source, descriptorTypes); | ||
for (var _i = 0, _a = Object.keys(descriptors); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
Object.defineProperty(target, key, descriptors[key]); | ||
} | ||
else if (typeof obj === 'function') { | ||
proto = obj.prototype; | ||
} | ||
else { | ||
throw new Error("Expected an object or a function. Got: " + obj); | ||
} | ||
if (!proto) | ||
return undefined; | ||
var methods = {}; | ||
for (var _i = 0, _a = Object.keys(proto); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
var desc = Object.getOwnPropertyDescriptor(proto, key); | ||
var hasGetter = desc && typeof desc.get === 'function'; | ||
if (!hasGetter && typeof proto[key] === 'function') { | ||
methods[key] = proto[key]; | ||
if (bind) { | ||
methods[key] = methods[key].bind(obj); | ||
return target; | ||
} | ||
function getAllPropertyDescriptors(obj, descriptorTypes) { | ||
var result = {}; | ||
while (obj.constructor !== Object) { | ||
var descriptors = Object.getOwnPropertyDescriptors(obj); | ||
if (descriptorTypes && descriptorTypes.length) { | ||
var filteredDescriptors = {}; | ||
for (var _i = 0, _a = Object.keys(descriptors); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
for (var _b = 0, descriptorTypes_1 = descriptorTypes; _b < descriptorTypes_1.length; _b++) { | ||
var flag = descriptorTypes_1[_b]; | ||
var shouldAdd = false; | ||
switch (flag) { | ||
case DescriptorType.None: | ||
break; | ||
case DescriptorType.Field: | ||
shouldAdd = (typeof descriptors[key].value !== 'function' && typeof descriptors[key].get !== 'function'); | ||
break; | ||
case DescriptorType.Property: | ||
shouldAdd = (typeof descriptors[key].get === 'function'); | ||
break; | ||
case DescriptorType.Method: | ||
shouldAdd = (typeof descriptors[key].value === 'function' && typeof descriptors[key].get !== 'function'); | ||
break; | ||
default: | ||
throw new Error("Property flag not supported: " + flag); | ||
} | ||
if (shouldAdd) | ||
filteredDescriptors[key] = descriptors[key]; | ||
} | ||
} | ||
descriptors = filteredDescriptors; | ||
} | ||
result = Object.assign(descriptors, result); | ||
obj = getPrototype(obj); | ||
} | ||
return methods; | ||
if (result.constructor) | ||
delete result.constructor; | ||
return result; | ||
} | ||
function getConstructorProp(obj, key) { | ||
if (!obj || !obj.constructor) | ||
return undefined; | ||
var ctor = obj.constructor; | ||
return ctor[key]; | ||
} | ||
function getConstructorOwnProp(obj, key) { | ||
@@ -341,37 +336,32 @@ if (!obj || !obj.constructor) | ||
} | ||
function getType(obj) { | ||
if (!obj) | ||
return undefined; | ||
if (typeof obj === 'function') | ||
return obj; | ||
if (typeof obj === 'object') | ||
return Object.getPrototypeOf(obj).constructor; | ||
throw new Error("Expected an object or a function. Got: " + obj); | ||
function getMethods(obj, bind) { | ||
if (bind === void 0) { bind = false; } | ||
var methodDescriptors = getAllPropertyDescriptors(obj, [DescriptorType.Method]); | ||
var methods = {}; | ||
for (var _i = 0, _a = Object.keys(methodDescriptors); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
methods[key] = methodDescriptors[key].value; | ||
if (bind) { | ||
methods[key] = methods[key].bind(obj); | ||
} | ||
} | ||
return methods; | ||
} | ||
function getParentType(obj) { | ||
var type = getType(obj); | ||
return Object.getPrototypeOf(type.prototype).constructor; | ||
} | ||
function isPlainObject(obj) { | ||
if (!obj) | ||
return false; | ||
if (typeof obj !== 'object') | ||
return false; | ||
if (typeof Object.getPrototypeOf === 'function') { | ||
var proto = Object.getPrototypeOf(obj); | ||
return proto === Object.prototype || proto === null; | ||
function getPrototype(obj) { | ||
if (typeof obj === 'object') { | ||
return Object.getPrototypeOf(obj); | ||
} | ||
return Object.prototype.toString.call(obj) === '[object Object]'; | ||
else if (typeof obj === 'function') { | ||
return obj.prototype; | ||
} | ||
else { | ||
throw new Error("Expected an object or a function. Got: " + obj); | ||
} | ||
} | ||
function toPlainObject(obj) { | ||
var json = JSON.stringify(obj, function (key, value) { return value === undefined ? null : value; }); | ||
return JSON.parse(json); | ||
function isPrimitive(val) { | ||
if (!val) | ||
return true; | ||
var type = typeof val; | ||
return type !== 'object' && type !== 'function'; | ||
} | ||
function clearProperties(obj) { | ||
var keys = Object.keys(obj); | ||
for (var _i = 0, keys_1 = keys; _i < keys_1.length; _i++) { | ||
var key = keys_1[_i]; | ||
delete obj[key]; | ||
} | ||
} | ||
@@ -383,58 +373,148 @@ // CONCATENATED MODULE: ./src/utils/index.ts | ||
// CONCATENATED MODULE: ./src/info/componentTemplateInfo.ts | ||
// CONCATENATED MODULE: ./src/info/creatorInfo.ts | ||
var creatorInfo_CreatorInfo = (function () { | ||
function CreatorInfo() { | ||
this.noDispatch = {}; | ||
this.sequence = {}; | ||
var componentTemplateInfo_ComponentTemplateInfo = (function () { | ||
function ComponentTemplateInfo() { | ||
this.actions = {}; | ||
this.sequences = {}; | ||
this.childIds = {}; | ||
} | ||
CreatorInfo.getInfo = function (obj) { | ||
ComponentTemplateInfo.getInfo = function (obj) { | ||
if (!obj) | ||
return undefined; | ||
var ownInfo = ComponentTemplateInfo.getOwnInfo(obj); | ||
if (ownInfo) | ||
return ownInfo; | ||
var baseInfo = ComponentTemplateInfo.getBaseInfo(obj); | ||
if (baseInfo) | ||
return ComponentTemplateInfo.initInfo(obj); | ||
return undefined; | ||
}; | ||
ComponentTemplateInfo.getOrInitInfo = function (obj) { | ||
var info = ComponentTemplateInfo.getInfo(obj); | ||
if (info) | ||
return info; | ||
return ComponentTemplateInfo.initInfo(obj); | ||
}; | ||
ComponentTemplateInfo.getOwnInfo = function (obj) { | ||
if (typeof obj === 'object') { | ||
return getConstructorOwnProp(obj, CREATOR_INFO); | ||
return getConstructorOwnProp(obj, COMPONENT_TEMPLATE_INFO); | ||
} | ||
else { | ||
return getOwnSymbol(obj, CREATOR_INFO); | ||
return getOwnSymbol(obj, COMPONENT_TEMPLATE_INFO); | ||
} | ||
}; | ||
CreatorInfo.getOrInitInfo = function (obj) { | ||
var info = CreatorInfo.getInfo(obj); | ||
if (!info) { | ||
var isConstructor = (typeof obj === 'function' ? true : false); | ||
var target = (isConstructor ? obj : obj.constructor); | ||
var baseInfo = getSymbol(target, CREATOR_INFO); | ||
var selfInfo = Object.assign(new CreatorInfo(), baseInfo); | ||
info = setSymbol(target, CREATOR_INFO, selfInfo); | ||
ComponentTemplateInfo.getBaseInfo = function (obj) { | ||
if (typeof obj === 'object') { | ||
return getConstructorProp(obj, COMPONENT_TEMPLATE_INFO); | ||
} | ||
return info; | ||
else { | ||
return getSymbol(obj, COMPONENT_TEMPLATE_INFO); | ||
} | ||
}; | ||
return CreatorInfo; | ||
ComponentTemplateInfo.initInfo = function (obj) { | ||
var isConstructor = (typeof obj === 'function' ? true : false); | ||
var target = (isConstructor ? obj : obj.constructor); | ||
var baseInfo = getSymbol(target, COMPONENT_TEMPLATE_INFO); | ||
var selfInfo = Object.assign(new ComponentTemplateInfo(), baseInfo); | ||
return setSymbol(target, COMPONENT_TEMPLATE_INFO, selfInfo); | ||
}; | ||
return ComponentTemplateInfo; | ||
}()); | ||
// CONCATENATED MODULE: ./src/info/creatorMethods.ts | ||
// CONCATENATED MODULE: ./src/info/index.ts | ||
function getCreatorMethods(obj, inherit) { | ||
if (inherit === void 0) { inherit = true; } | ||
if (!creatorInfo_CreatorInfo.getInfo(obj)) | ||
return undefined; | ||
var methods = getMethods(obj); | ||
if (inherit) { | ||
var parentType = getParentType(obj); | ||
while (parentType !== Object) { | ||
var parentMethods = getCreatorMethods(parentType, false); | ||
methods = Object.assign({}, parentMethods, methods); | ||
parentType = getParentType(parentType); | ||
// CONCATENATED MODULE: ./src/decorators/action.ts | ||
function action_action(target, propertyKey) { | ||
var info = componentTemplateInfo_ComponentTemplateInfo.getOrInitInfo(target); | ||
info.actions[propertyKey] = true; | ||
} | ||
// CONCATENATED MODULE: ./src/decorators/ignoreState.ts | ||
function ignoreState(target, propertyKey) { | ||
var info = classInfo_ClassInfo.getOrInitInfo(target); | ||
info.ignoreState[propertyKey] = true; | ||
} | ||
var ignoreState_IgnoreState = (function () { | ||
function IgnoreState() { | ||
} | ||
IgnoreState.isIgnoredProperty = function (propHolder, propKey) { | ||
var info = classInfo_ClassInfo.getInfo(propHolder); | ||
return info && info.ignoreState[propKey]; | ||
}; | ||
IgnoreState.removeIgnoredProps = function (state, obj) { | ||
var info = classInfo_ClassInfo.getInfo(obj); | ||
if (!info) | ||
return state; | ||
for (var _i = 0, _a = Object.keys(info.ignoreState); _i < _a.length; _i++) { | ||
var propKey = _a[_i]; | ||
delete state[propKey]; | ||
} | ||
return state; | ||
}; | ||
return IgnoreState; | ||
}()); | ||
// CONCATENATED MODULE: ./src/decorators/sequence.ts | ||
function sequence(target, propertyKey) { | ||
var info = componentTemplateInfo_ComponentTemplateInfo.getOrInitInfo(target); | ||
info.sequences[propertyKey] = true; | ||
} | ||
// CONCATENATED MODULE: ./src/decorators/withId.ts | ||
function withId(targetOrId, propertyKeyOrNothing) { | ||
if (propertyKeyOrNothing) { | ||
withIdDecorator.call(undefined, targetOrId, propertyKeyOrNothing); | ||
} | ||
return methods; | ||
else { | ||
return function (target, propertyKey) { return withIdDecorator(target, propertyKey, targetOrId); }; | ||
} | ||
} | ||
function withIdDecorator(target, propertyKey, id) { | ||
var info = componentTemplateInfo_ComponentTemplateInfo.getOrInitInfo(target); | ||
info.childIds[propertyKey] = id || AUTO_ID; | ||
} | ||
var withId_ComponentId = (function () { | ||
function ComponentId() { | ||
} | ||
ComponentId.nextAvailableId = function () { | ||
return --ComponentId.autoComponentId; | ||
}; | ||
ComponentId.getComponentId = function (parentTemplate, path) { | ||
var pathArray = path.split('.'); | ||
if (!parentTemplate || !pathArray.length) | ||
return undefined; | ||
var info = componentTemplateInfo_ComponentTemplateInfo.getInfo(parentTemplate); | ||
if (!info) | ||
return; | ||
var selfKey = pathArray[pathArray.length - 1]; | ||
var id = info.childIds[selfKey]; | ||
if (!id) | ||
return undefined; | ||
if (id === AUTO_ID) { | ||
var generatedId = ComponentId.nextAvailableId(); | ||
log.verbose('[getComponentId] new component id generated: ' + generatedId); | ||
info.childIds[selfKey] = generatedId; | ||
return generatedId; | ||
} | ||
return id; | ||
}; | ||
ComponentId.autoComponentId = 0; | ||
return ComponentId; | ||
}()); | ||
// CONCATENATED MODULE: ./src/info/index.ts | ||
// CONCATENATED MODULE: ./src/decorators/index.ts | ||
@@ -444,2 +524,3 @@ | ||
// EXTERNAL MODULE: external "redux" | ||
@@ -479,3 +560,3 @@ var external__redux_ = __webpack_require__(2); | ||
var reduxApp_ReduxApp = (function () { | ||
function ReduxApp(appCreator) { | ||
function ReduxApp(appTemplate) { | ||
var params = []; | ||
@@ -487,3 +568,3 @@ for (var _i = 1; _i < arguments.length; _i++) { | ||
this.initialStateUpdated = false; | ||
var _a = this.resolveParameters(appCreator, params), options = _a.options, preLoadedState = _a.preLoadedState, enhancer = _a.enhancer; | ||
var _a = this.resolveParameters(appTemplate, params), options = _a.options, preLoadedState = _a.preLoadedState, enhancer = _a.enhancer; | ||
this.name = this.getAppName(options.name); | ||
@@ -496,4 +577,5 @@ if (appsRepository[this.name]) | ||
var creationContext = new component_ComponentCreationContext({ appName: this.name }); | ||
var rootComponent = component_Component.create(this.store, appCreator, creationContext); | ||
var rootComponent = component_Component.create(this.store, appTemplate, creationContext); | ||
this.root = rootComponent; | ||
this.registerComponents(creationContext.createdComponents); | ||
var reducersContext = new reducer_CombineReducersContext({ | ||
@@ -504,3 +586,3 @@ componentPaths: Object.keys(creationContext.createdComponents) | ||
if (options.updateState) { | ||
var stateListener = this.updateState(creationContext.createdComponents, reducersContext); | ||
var stateListener = this.updateState(reducersContext); | ||
this.subscriptionDisposer = this.store.subscribe(stateListener); | ||
@@ -510,3 +592,3 @@ } | ||
} | ||
ReduxApp.createApp = function (appCreator) { | ||
ReduxApp.createApp = function (appTemplate) { | ||
var params = []; | ||
@@ -516,31 +598,28 @@ for (var _i = 1; _i < arguments.length; _i++) { | ||
} | ||
return new (ReduxApp.bind.apply(ReduxApp, [void 0, appCreator].concat(params)))(); | ||
return new (ReduxApp.bind.apply(ReduxApp, [void 0, appTemplate].concat(params)))(); | ||
}; | ||
ReduxApp.getApp = function (appId) { | ||
var applicationId = appId || DEFAULT_APP_NAME; | ||
var app = appsRepository[applicationId]; | ||
if (!app) | ||
log.debug("[ReduxApp] Application '" + applicationId + "' does not exist."); | ||
return app; | ||
}; | ||
ReduxApp.getComponent = function (type, componentId, appId) { | ||
var app = ReduxApp.getApp(appId); | ||
if (!app) | ||
return undefined; | ||
throw new Error("App not found (id: '" + (appId || DEFAULT_APP_NAME) + "')"); | ||
var warehouse = app.getTypeWarehouse(type); | ||
if (componentId) { | ||
return warehouse.get(componentId); | ||
var comp = warehouse.get(componentId); | ||
if (!comp) | ||
throw new Error("Component not found. Type: " + type.name + ". Id: '" + componentId + "'."); | ||
return comp; | ||
} | ||
else { | ||
return warehouse.values().next().value; | ||
var comp = warehouse.values().next().value; | ||
if (!comp) | ||
throw new Error("Component not found. Type: " + type.name + "."); | ||
return comp; | ||
} | ||
}; | ||
ReduxApp.registerComponent = function (comp, creator, appName) { | ||
appName = appName || DEFAULT_APP_NAME; | ||
var app = appsRepository[appName]; | ||
if (app) { | ||
var warehouse = app.getTypeWarehouse(creator.constructor); | ||
var key = componentInfo_ComponentInfo.getInfo(comp).id || withId_ComponentId.nextAvailableId(); | ||
warehouse.set(key, comp); | ||
} | ||
ReduxApp.getApp = function (appId) { | ||
var applicationId = appId || DEFAULT_APP_NAME; | ||
var app = appsRepository[applicationId]; | ||
if (!app) | ||
log.debug("[ReduxApp] Application '" + applicationId + "' does not exist."); | ||
return app; | ||
}; | ||
@@ -556,12 +635,7 @@ ReduxApp.prototype.dispose = function () { | ||
}; | ||
ReduxApp.prototype.getTypeWarehouse = function (type) { | ||
if (!this.warehouse.has(type)) | ||
this.warehouse.set(type, new Map()); | ||
return this.warehouse.get(type); | ||
}; | ||
ReduxApp.prototype.resolveParameters = function (appCreator, params) { | ||
ReduxApp.prototype.resolveParameters = function (appTemplate, params) { | ||
var result = {}; | ||
if (params.length === 0) { | ||
result.options = new AppOptions(); | ||
result.preLoadedState = appCreator; | ||
result.preLoadedState = appTemplate; | ||
} | ||
@@ -572,7 +646,7 @@ else if (params.length === 1) { | ||
result.enhancer = params[0]; | ||
result.preLoadedState = appCreator; | ||
result.preLoadedState = appTemplate; | ||
} | ||
else { | ||
result.options = Object.assign(new AppOptions(), params[0]); | ||
result.preLoadedState = appCreator; | ||
result.preLoadedState = appTemplate; | ||
} | ||
@@ -601,5 +675,18 @@ } | ||
}; | ||
ReduxApp.prototype.updateState = function (allComponents, reducersContext) { | ||
ReduxApp.prototype.registerComponents = function (components) { | ||
for (var _i = 0, _a = Object.values(components); _i < _a.length; _i++) { | ||
var comp = _a[_i]; | ||
var compInfo = componentInfo_ComponentInfo.getInfo(comp); | ||
var warehouse = this.getTypeWarehouse(compInfo.originalClass); | ||
var key = compInfo.id || withId_ComponentId.nextAvailableId(); | ||
warehouse.set(key, comp); | ||
} | ||
}; | ||
ReduxApp.prototype.getTypeWarehouse = function (type) { | ||
if (!this.warehouse.has(type)) | ||
this.warehouse.set(type, new Map()); | ||
return this.warehouse.get(type); | ||
}; | ||
ReduxApp.prototype.updateState = function (reducersContext) { | ||
var _this = this; | ||
var withComputedProps = computed_Computed.filterComponents(Object.values(allComponents)); | ||
return function () { | ||
@@ -615,3 +702,2 @@ var start = Date.now(); | ||
} | ||
computed_Computed.computeProps(withComputedProps); | ||
reducersContext.reset(); | ||
@@ -641,6 +727,3 @@ var end = Date.now(); | ||
context.visited.add(obj); | ||
var newStateType = newState.constructor; | ||
if (globalOptions.convertToPlainObject) | ||
newState = toPlainObject(newState); | ||
if (context.forceRecursion || (obj instanceof component_Component && newStateType === Object)) { | ||
if (context.forceRecursion || (obj instanceof component_Component)) { | ||
var changeMessage; | ||
@@ -665,8 +748,11 @@ if (Array.isArray(obj) && Array.isArray(newState)) { | ||
ReduxApp.prototype.updateObject = function (obj, newState, context) { | ||
var _this = this; | ||
var propsDeleted = []; | ||
Object.keys(obj).forEach(function (key) { | ||
for (var _i = 0, _a = Object.keys(obj); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
if (ignoreState_IgnoreState.isIgnoredProperty(obj, key)) | ||
return; | ||
continue; | ||
if (!newState.hasOwnProperty(key)) { | ||
var desc = Object.getOwnPropertyDescriptor(obj, key); | ||
if (desc && typeof desc.get === 'function') | ||
continue; | ||
if (typeof obj[key] === 'function') | ||
@@ -677,14 +763,14 @@ log.warn("[updateState] Function property removed in path: " + context.path + "." + key + ". Consider using a method instead."); | ||
} | ||
}); | ||
} | ||
var propsAssigned = []; | ||
Object.keys(newState).forEach(function (key) { | ||
if (connect_Connect.isConnectedProperty(obj, key)) | ||
return; | ||
if (computed_Computed.isComputedProperty(obj, key)) | ||
return; | ||
for (var _b = 0, _c = Object.keys(newState); _b < _c.length; _b++) { | ||
var key = _c[_b]; | ||
if (ignoreState_IgnoreState.isIgnoredProperty(obj, key)) | ||
return; | ||
continue; | ||
var desc = Object.getOwnPropertyDescriptor(obj, key); | ||
if (desc && typeof desc.get === 'function' && typeof desc.set !== 'function') | ||
continue; | ||
var subState = newState[key]; | ||
var subObj = obj[key]; | ||
var newSubObj = _this.updateStateRecursion(subObj, subState, __assign({}, context, { path: context.path + '.' + key })); | ||
var newSubObj = this.updateStateRecursion(subObj, subState, __assign({}, context, { path: context.path + '.' + key })); | ||
if (newSubObj !== subObj) { | ||
@@ -694,3 +780,3 @@ obj[key] = newSubObj; | ||
} | ||
}); | ||
} | ||
if (propsAssigned.length || propsDeleted.length) { | ||
@@ -738,300 +824,2 @@ var propsAssignedMessage = propsAssigned.length ? "Props assigned: " + propsAssigned.join(', ') + "." : ''; | ||
// CONCATENATED MODULE: ./src/decorators/connect/connectOptions.ts | ||
var connectOptions_ConnectOptions = (function () { | ||
function ConnectOptions() { | ||
this.app = DEFAULT_APP_NAME; | ||
this.live = false; | ||
} | ||
return ConnectOptions; | ||
}()); | ||
// CONCATENATED MODULE: ./src/decorators/connect/connect.ts | ||
var connect_Connect = (function () { | ||
function Connect() { | ||
} | ||
Connect.isConnectedProperty = function (propHolder, propKey) { | ||
var info = classInfo_ClassInfo.getInfo(propHolder); | ||
return info && info.connectedProps[propKey]; | ||
}; | ||
Connect.setupConnectedProps = function (target, targetInfo, source, sourceInfo) { | ||
if (!sourceInfo) | ||
return; | ||
for (var _i = 0, _a = Object.keys(sourceInfo.connectedProps); _i < _a.length; _i++) { | ||
var propKey = _a[_i]; | ||
source[propKey]; | ||
var desc = Object.getOwnPropertyDescriptor(source, propKey); | ||
Object.defineProperty(target, propKey, desc); | ||
} | ||
targetInfo.connectedProps = sourceInfo.connectedProps; | ||
}; | ||
Connect.removeConnectedProps = function (state, obj, connectedProps) { | ||
var info = classInfo_ClassInfo.getInfo(obj); | ||
if (!info) | ||
return state; | ||
Object.assign(connectedProps, info.connectedProps); | ||
var newState = Object.assign({}, state); | ||
for (var _i = 0, _a = Object.keys(info.connectedProps); _i < _a.length; _i++) { | ||
var propKey = _a[_i]; | ||
var sourceInfoString = ''; | ||
var sourceInfo = componentInfo_ComponentInfo.getInfo(obj[propKey]); | ||
if (sourceInfo) { | ||
var sourceIdString = (sourceInfo.id !== undefined ? '.' + sourceInfo.id : ''); | ||
sourceInfoString = '.' + sourceInfo.originalClass.name + sourceIdString; | ||
} | ||
newState[propKey] = Connect.placeholderPrefix + sourceInfoString + '>'; | ||
} | ||
return newState; | ||
}; | ||
Connect.placeholderPrefix = '<connected'; | ||
return Connect; | ||
}()); | ||
// EXTERNAL MODULE: external "reflect-metadata" | ||
var external__reflect_metadata_ = __webpack_require__(4); | ||
var external__reflect_metadata__default = /*#__PURE__*/__webpack_require__.n(external__reflect_metadata_); | ||
// CONCATENATED MODULE: ./src/decorators/connect/decorator.ts | ||
function connect(targetOrOptions, propertyKeyOrNothing) { | ||
if (propertyKeyOrNothing) { | ||
connectDecorator.call(undefined, targetOrOptions, propertyKeyOrNothing); | ||
} | ||
else { | ||
return function (target, propertyKey) { return connectDecorator(target, propertyKey, targetOrOptions); }; | ||
} | ||
} | ||
function connectDecorator(target, propertyKey, options) { | ||
options = Object.assign(new connectOptions_ConnectOptions(), options); | ||
var value = target[propertyKey]; | ||
var info = classInfo_ClassInfo.getOrInitInfo(target); | ||
info.connectedProps[propertyKey] = true; | ||
var type = Reflect.getMetadata("design:type", target, propertyKey); | ||
if (!type) { | ||
var reflectErrMsg = "[connect] Failed to reflect type of property '" + propertyKey + "'. " + | ||
"Make sure you're using TypeScript and that the 'emitDecoratorMetadata' compiler " + | ||
"option in your tsconfig.json file is turned on. " + | ||
"Note that even if TypeScript is configured correctly it may fail to reflect " + | ||
"property types due to the loading order of your classes. " + | ||
("In that case, make sure that the type of '" + propertyKey + "' is loaded prior to the ") + | ||
("type of it's containing class (" + target.constructor.name + ")."); | ||
throw new Error(reflectErrMsg); | ||
} | ||
var oldDescriptor = Object.getOwnPropertyDescriptor(target, propertyKey); | ||
var newDescriptor = { | ||
get: function () { | ||
var result = reduxApp_ReduxApp.getComponent(type, options.id, options.app); | ||
if (result && !options.live) { | ||
Object.defineProperty(this, propertyKey, dataDescriptor); | ||
value = this[propertyKey] = result; | ||
log.verbose("[connect] Property '" + propertyKey + "' connected. Type: " + type.name + "."); | ||
} | ||
return result; | ||
}, | ||
set: function (newValue) { | ||
var app = appsRepository[options.app]; | ||
if (app) { | ||
log.warn("[connect] Connected component '" + propertyKey + "' value assigned. Component disconnected."); | ||
} | ||
if (oldDescriptor && oldDescriptor.set) { | ||
return oldDescriptor.set(newValue); | ||
} | ||
else if (!oldDescriptor || oldDescriptor && oldDescriptor.writable) { | ||
return value = newValue; | ||
} | ||
} | ||
}; | ||
return deferredDefineProperty(target, propertyKey, Object.assign({}, accessorDescriptor, newDescriptor)); | ||
} | ||
// CONCATENATED MODULE: ./src/decorators/connect/index.ts | ||
// CONCATENATED MODULE: ./src/decorators/component.ts | ||
function component_component(ctorOrOptions) { | ||
if (typeof ctorOrOptions === 'function') { | ||
componentDecorator.call(undefined, ctorOrOptions); | ||
} | ||
else { | ||
return function (ctor) { return componentDecorator(ctor, ctorOrOptions); }; | ||
} | ||
} | ||
function componentDecorator(ctor, options) { | ||
var info = creatorInfo_CreatorInfo.getOrInitInfo(ctor); | ||
info.options = options; | ||
} | ||
// CONCATENATED MODULE: ./src/decorators/computed.ts | ||
var isEqual = __webpack_require__(5); | ||
function computed(target, propertyKey) { | ||
var descriptor = Object.getOwnPropertyDescriptor(target, propertyKey); | ||
if (typeof descriptor.get !== 'function') | ||
throw new Error("Failed to decorate '" + propertyKey + "'. The 'computed' decorator should only be used on getters."); | ||
if (descriptor.set) | ||
throw new Error("Failed to decorate '" + propertyKey + "'. Decorated property should not have a setter."); | ||
var info = classInfo_ClassInfo.getOrInitInfo(target); | ||
info.computedGetters[propertyKey] = descriptor.get; | ||
return deferredDefineProperty(target, propertyKey, dataDescriptor); | ||
} | ||
var computed_Computed = (function () { | ||
function Computed() { | ||
} | ||
Computed.isComputedProperty = function (propHolder, propKey) { | ||
var info = classInfo_ClassInfo.getInfo(propHolder); | ||
return info && (typeof info.computedGetters[propKey] === 'function'); | ||
}; | ||
Computed.removeComputedProps = function (state, obj, computedProps) { | ||
var info = classInfo_ClassInfo.getInfo(obj); | ||
if (!info) | ||
return state; | ||
Object.assign(computedProps, info.computedGetters); | ||
var newState = Object.assign({}, state); | ||
for (var _i = 0, _a = Object.keys(info.computedGetters); _i < _a.length; _i++) { | ||
var propKey = _a[_i]; | ||
newState[propKey] = Computed.placeholder; | ||
} | ||
return newState; | ||
}; | ||
Computed.filterComponents = function (components) { | ||
return components.filter(function (comp) { | ||
var info = classInfo_ClassInfo.getInfo(comp); | ||
return info && Object.keys(info.computedGetters); | ||
}); | ||
}; | ||
Computed.computeProps = function (components) { | ||
for (var _i = 0, components_1 = components; _i < components_1.length; _i++) { | ||
var comp = components_1[_i]; | ||
this.computeObjectProps(comp); | ||
} | ||
}; | ||
Computed.computeObjectProps = function (obj) { | ||
var info = classInfo_ClassInfo.getInfo(obj); | ||
if (!info) | ||
return; | ||
for (var _i = 0, _a = Object.keys(info.computedGetters); _i < _a.length; _i++) { | ||
var propKey = _a[_i]; | ||
var getter = info.computedGetters[propKey]; | ||
var newValue = getter.call(obj); | ||
var oldValue = obj[propKey]; | ||
if (newValue !== oldValue && (!globalOptions.computed.deepComparison || !isEqual(newValue, oldValue))) { | ||
obj[propKey] = newValue; | ||
} | ||
} | ||
}; | ||
Computed.placeholder = '<computed>'; | ||
return Computed; | ||
}()); | ||
// CONCATENATED MODULE: ./src/decorators/ignoreState.ts | ||
function ignoreState(target, propertyKey) { | ||
var info = classInfo_ClassInfo.getOrInitInfo(target); | ||
info.ignoreState[propertyKey] = true; | ||
} | ||
var ignoreState_IgnoreState = (function () { | ||
function IgnoreState() { | ||
} | ||
IgnoreState.isIgnoredProperty = function (propHolder, propKey) { | ||
var info = classInfo_ClassInfo.getInfo(propHolder); | ||
return info && info.ignoreState[propKey]; | ||
}; | ||
IgnoreState.removeIgnoredProps = function (state, obj, ignoredProps) { | ||
var info = classInfo_ClassInfo.getInfo(obj); | ||
if (!info) | ||
return state; | ||
Object.assign(ignoredProps, info.ignoreState); | ||
var newState = Object.assign({}, state); | ||
for (var _i = 0, _a = Object.keys(info.ignoreState); _i < _a.length; _i++) { | ||
var propKey = _a[_i]; | ||
delete newState[propKey]; | ||
} | ||
return newState; | ||
}; | ||
return IgnoreState; | ||
}()); | ||
// CONCATENATED MODULE: ./src/decorators/noDispatch.ts | ||
function noDispatch(target, propertyKey) { | ||
var info = creatorInfo_CreatorInfo.getOrInitInfo(target); | ||
info.noDispatch[propertyKey] = true; | ||
} | ||
// CONCATENATED MODULE: ./src/decorators/sequence.ts | ||
function sequence(target, propertyKey) { | ||
var info = creatorInfo_CreatorInfo.getOrInitInfo(target); | ||
info.sequence[propertyKey] = true; | ||
} | ||
// CONCATENATED MODULE: ./src/decorators/withId.ts | ||
function withId(targetOrId, propertyKeyOrNothing) { | ||
if (propertyKeyOrNothing) { | ||
withIdDecorator.call(undefined, targetOrId, propertyKeyOrNothing); | ||
} | ||
else { | ||
return function (target, propertyKey) { return withIdDecorator(target, propertyKey, targetOrId); }; | ||
} | ||
} | ||
function withIdDecorator(target, propertyKey, id) { | ||
var info = creatorInfo_CreatorInfo.getOrInitInfo(target); | ||
info.childIds[propertyKey] = id || AUTO_ID; | ||
} | ||
var withId_ComponentId = (function () { | ||
function ComponentId() { | ||
} | ||
ComponentId.nextAvailableId = function () { | ||
return --ComponentId.autoComponentId; | ||
}; | ||
ComponentId.getComponentId = function (parentCreator, path) { | ||
var pathArray = path.split('.'); | ||
if (!parentCreator || !pathArray.length) | ||
return undefined; | ||
var info = creatorInfo_CreatorInfo.getInfo(parentCreator); | ||
if (!info) | ||
return; | ||
var selfKey = pathArray[pathArray.length - 1]; | ||
var id = info.childIds[selfKey]; | ||
if (!id) | ||
return undefined; | ||
if (id === AUTO_ID) { | ||
var generatedId = ComponentId.nextAvailableId(); | ||
log.verbose('[getComponentId] new component id generated: ' + generatedId); | ||
info.childIds[selfKey] = generatedId; | ||
return generatedId; | ||
} | ||
return id; | ||
}; | ||
ComponentId.autoComponentId = 0; | ||
return ComponentId; | ||
}()); | ||
// CONCATENATED MODULE: ./src/decorators/index.ts | ||
// CONCATENATED MODULE: ./src/components/reducer.ts | ||
@@ -1070,18 +858,22 @@ var reducer___assign = (this && this.__assign) || Object.assign || function(t) { | ||
} | ||
ComponentReducer.createReducer = function (component, componentCreator) { | ||
var creatorInfo = creatorInfo_CreatorInfo.getInfo(componentCreator); | ||
if (!creatorInfo) | ||
throw new Error("Inconsistent component '" + componentCreator.constructor.name + "'. The 'component' class decorator is missing."); | ||
var methods = ComponentReducer.createMethodsLookup(componentCreator, creatorInfo); | ||
var stateProto = ComponentReducer.createStateObjectPrototype(component, creatorInfo); | ||
ComponentReducer.createReducer = function (component, componentTemplate) { | ||
var templateInfo = componentTemplateInfo_ComponentTemplateInfo.getInfo(componentTemplate); | ||
if (!templateInfo) | ||
throw new Error("Inconsistent component '" + componentTemplate.constructor.name + "'. The 'component' class decorator is missing."); | ||
var methods = ComponentReducer.createMethodsLookup(componentTemplate, templateInfo); | ||
var stateProto = ComponentReducer.createStateObjectPrototype(component, templateInfo); | ||
var componentId = componentInfo_ComponentInfo.getInfo(component).id; | ||
return function (changeListener) { | ||
return function (state, action) { | ||
log.verbose("[reducer] Reducer of: " + componentCreator.constructor.name + ", action: " + action.type); | ||
log.verbose("[reducer] Reducer of: " + componentTemplate.constructor.name + ", action: " + action.type + "."); | ||
if (state === undefined) { | ||
log.verbose('[reducer] State is undefined, returning initial value'); | ||
return component; | ||
log.verbose('[reducer] State is undefined, returning initial value.'); | ||
return ComponentReducer.finalizeStateObject(component, component); | ||
} | ||
if (state === componentTemplate) { | ||
log.verbose("[reducer] State equals to component's template, returning initial value."); | ||
return ComponentReducer.finalizeStateObject(component, component); | ||
} | ||
if (componentId !== action.id) { | ||
log.verbose("[reducer] Component id and action.id don't match (" + componentId + " !== " + action.id + ")"); | ||
log.verbose("[reducer] Component id and action.id don't match (" + componentId + " !== " + action.id + ")."); | ||
return state; | ||
@@ -1091,3 +883,3 @@ } | ||
if (!actionReducer) { | ||
log.verbose('[reducer] No matching action in this reducer, returning previous state'); | ||
log.verbose('[reducer] No matching action in this reducer, returning previous state.'); | ||
return state; | ||
@@ -1098,4 +890,4 @@ } | ||
changeListener(component); | ||
log.verbose('[reducer] Reducer invoked, returning new state'); | ||
return newState; | ||
log.verbose('[reducer] Reducer invoked, returning new state.'); | ||
return ComponentReducer.finalizeStateObject(newState, component); | ||
}; | ||
@@ -1111,3 +903,2 @@ }; | ||
var newState = reducer(state, action); | ||
newState = ComponentReducer.finalizeState(newState, root); | ||
var end = Date.now(); | ||
@@ -1118,20 +909,17 @@ log.debug("[rootReducer] Reducer tree processed in " + (end - start) + "ms."); | ||
}; | ||
ComponentReducer.createMethodsLookup = function (componentCreator, creatorInfo) { | ||
var allMethods = getCreatorMethods(componentCreator); | ||
var options = creatorInfo.options; | ||
ComponentReducer.createMethodsLookup = function (componentTemplate, templateInfo) { | ||
var allMethods = getMethods(componentTemplate); | ||
var actionMethods = {}; | ||
Object.keys(allMethods).forEach(function (methName) { | ||
if (creatorInfo.noDispatch[methName] || creatorInfo.sequence[methName]) | ||
return; | ||
var actionName = actions_ComponentActions.getActionName(componentCreator, methName, options); | ||
actionMethods[actionName] = allMethods[methName]; | ||
Object.keys(templateInfo.actions).forEach(function (originalActionName) { | ||
var normalizedActionName = actions_ComponentActions.getActionName(componentTemplate, originalActionName); | ||
actionMethods[normalizedActionName] = allMethods[originalActionName]; | ||
}); | ||
return actionMethods; | ||
}; | ||
ComponentReducer.createStateObjectPrototype = function (component, creatorInfo) { | ||
var stateProto = {}; | ||
ComponentReducer.createStateObjectPrototype = function (component, templateInfo) { | ||
var stateProto = defineProperties({}, component, [DescriptorType.Property]); | ||
var componentMethods = getMethods(component); | ||
for (var _i = 0, _a = Object.keys(componentMethods); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
if (creatorInfo.noDispatch[key]) { | ||
if (!templateInfo.actions[key]) { | ||
stateProto[key] = componentMethods[key].bind(component); | ||
@@ -1146,9 +934,22 @@ } | ||
ComponentReducer.actionInvokedError = function () { | ||
throw new Error("Only 'noDispatch' methods can be invoked inside actions."); | ||
throw new Error("Actions should not be invoked from within other actions."); | ||
}; | ||
ComponentReducer.createStateObject = function (state, stateProto) { | ||
var stateObj = Object.create(stateProto); | ||
Object.assign(stateObj, state); | ||
for (var _i = 0, _a = Object.keys(state); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
var desc = Object.getOwnPropertyDescriptor(stateProto, key); | ||
if (desc && typeof desc.get === 'function' && typeof desc.set !== 'function') | ||
continue; | ||
stateObj[key] = state[key]; | ||
} | ||
return stateObj; | ||
}; | ||
ComponentReducer.finalizeStateObject = function (state, component) { | ||
log.verbose('[finalizeStateObject] finalizing state.'); | ||
var finalizedState = Object.assign({}, state); | ||
finalizedState = ignoreState_IgnoreState.removeIgnoredProps(finalizedState, component); | ||
log.verbose('[finalizeStateObject] state finalized.'); | ||
return finalizedState; | ||
}; | ||
ComponentReducer.combineReducersRecursion = function (obj, context) { | ||
@@ -1175,4 +976,2 @@ if (isPrimitive(obj)) | ||
var key = _a[_i]; | ||
if (connect_Connect.isConnectedProperty(obj, key)) | ||
continue; | ||
var newSubReducer = ComponentReducer.combineReducersRecursion(obj[key], new reducer_CombineReducersContext(reducer___assign({}, context, { path: (context.path === '' ? key : context.path + '.' + key) }))); | ||
@@ -1204,27 +1003,2 @@ if (typeof newSubReducer === 'function') | ||
}; | ||
ComponentReducer.finalizeState = function (rootState, root) { | ||
return ComponentReducer.finalizeStateRecursion(rootState, root, new Set()); | ||
}; | ||
ComponentReducer.finalizeStateRecursion = function (state, obj, visited) { | ||
if (isPrimitive(state) || isPrimitive(obj)) | ||
return state; | ||
if (visited.has(state)) | ||
return state; | ||
visited.add(state); | ||
var handledProps = {}; | ||
state = connect_Connect.removeConnectedProps(state, obj, handledProps); | ||
state = computed_Computed.removeComputedProps(state, obj, handledProps); | ||
state = ignoreState_IgnoreState.removeIgnoredProps(state, obj, handledProps); | ||
Object.keys(state).forEach(function (key) { | ||
if (handledProps.hasOwnProperty(key)) | ||
return; | ||
var subState = state[key]; | ||
var subObj = obj[key]; | ||
var newSubState = ComponentReducer.finalizeStateRecursion(subState, subObj, visited); | ||
if (newSubState !== subState) { | ||
state[key] = newSubState; | ||
} | ||
}); | ||
return state; | ||
}; | ||
ComponentReducer.identityReducer = function (state) { return state; }; | ||
@@ -1236,12 +1010,2 @@ return ComponentReducer; | ||
// CONCATENATED MODULE: ./src/components/component.ts | ||
var __extends = (this && this.__extends) || (function () { | ||
var extendStatics = Object.setPrototypeOf || | ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || | ||
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; | ||
return function (d, b) { | ||
extendStatics(d, b); | ||
function __() { this.constructor = d; } | ||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); | ||
}; | ||
})(); | ||
var component___assign = (this && this.__assign) || Object.assign || function(t) { | ||
@@ -1261,6 +1025,6 @@ for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
var component_ComponentCreationContext = (function () { | ||
function ComponentCreationContext(initial) { | ||
this.visited = new Set(); | ||
this.visitedNodes = new Set(); | ||
this.visitedTemplates = new Map(); | ||
this.path = ROOT_COMPONENT_PATH; | ||
@@ -1274,78 +1038,64 @@ this.createdComponents = {}; | ||
var component_Component = (function () { | ||
function Component(store, creator, context) { | ||
if (!creatorInfo_CreatorInfo.getInfo(creator)) | ||
throw new Error("Argument '" + "creator" + "' is not a component creator. Did you forget to use the decorator?"); | ||
Component.createSelf(this, store, creator, context); | ||
Component.createSubComponents(this, store, creator, context); | ||
function Component(store, template, context) { | ||
if (!componentTemplateInfo_ComponentTemplateInfo.getInfo(template)) | ||
throw new Error("Argument '" + "template" + "' is not a component template. Did you forget to use the decorator?"); | ||
Component.createSelf(this, store, template, context); | ||
context.createdComponents[context.path] = this; | ||
log.verbose("[Component] New " + creator.constructor.name + " component created. Path: " + context.path); | ||
context.visitedTemplates.set(template, this); | ||
log.verbose("[Component] New " + template.constructor.name + " component created. Path: " + context.path); | ||
Component.createSubComponents(this, store, template, context); | ||
} | ||
Component.create = function (store, creator, context) { | ||
Component.create = function (store, template, context) { | ||
context = Object.assign(new component_ComponentCreationContext(), context); | ||
var ComponentClass = Component.getComponentClass(creator); | ||
var component = new ComponentClass(store, creator, context); | ||
reduxApp_ReduxApp.registerComponent(component, creator, context.appName); | ||
componentTemplateInfo_ComponentTemplateInfo.getOrInitInfo(template); | ||
var ComponentClass = Component.getComponentClass(template); | ||
var component = new ComponentClass(store, template, context); | ||
return component; | ||
}; | ||
Component.getComponentClass = function (creator) { | ||
var info = creatorInfo_CreatorInfo.getInfo(creator); | ||
Component.getComponentClass = function (template) { | ||
var info = componentTemplateInfo_ComponentTemplateInfo.getInfo(template); | ||
if (!info.componentClass) { | ||
info.componentClass = Component.createComponentClass(creator); | ||
info.originalClass = creator.constructor; | ||
info.componentClass = Component.createComponentClass(template); | ||
} | ||
return info.componentClass; | ||
}; | ||
Component.createComponentClass = function (creator) { | ||
var ComponentClass = (function (_super) { | ||
__extends(ComponentClass, _super); | ||
function ComponentClass(store, creatorArg, context) { | ||
var _this = _super.call(this, store, creatorArg, context) || this; | ||
_this.__originalClassName__ = creator.constructor.name; | ||
if (!globalOptions.emitClassNames) | ||
delete _this.__originalClassName__; | ||
return _this; | ||
} | ||
return ComponentClass; | ||
}(Component)); | ||
var actions = actions_ComponentActions.createActions(creator); | ||
Component.createComponentClass = function (template) { | ||
var componentClassFactory = new Function('initCallback', "\"use strict\";return function " + template.constructor.name + "_ReduxAppComponent() { initCallback(this, arguments); }"); | ||
var ComponentClass = componentClassFactory(function (self, args) { return Component.apply(self, args); }); | ||
ComponentClass.prototype = Object.create(Component.prototype); | ||
ComponentClass.prototype.constructor = ComponentClass; | ||
var actions = actions_ComponentActions.createActions(template); | ||
Object.assign(ComponentClass.prototype, actions); | ||
return ComponentClass; | ||
}; | ||
Component.createSelf = function (component, store, creator, context) { | ||
for (var _i = 0, _a = Object.getOwnPropertyNames(creator); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
var desc = Object.getOwnPropertyDescriptor(creator, key); | ||
Object.defineProperty(component, key, desc); | ||
} | ||
var selfInfo = componentInfo_ComponentInfo.initInfo(component); | ||
Component.createSelf = function (component, store, template, context) { | ||
defineProperties(component, template, [DescriptorType.Field, DescriptorType.Property]); | ||
var id = withId_ComponentId.getComponentId(context.parentTemplate, context.path); | ||
var selfInfo = componentInfo_ComponentInfo.initInfo(component, template, store.dispatch, id); | ||
var selfClassInfo = classInfo_ClassInfo.getOrInitInfo(component); | ||
var creatorInfo = creatorInfo_CreatorInfo.getInfo(creator); | ||
var creatorClassInfo = classInfo_ClassInfo.getInfo(creator) || new classInfo_ClassInfo(); | ||
selfInfo.id = withId_ComponentId.getComponentId(context.parentCreator, context.path); | ||
selfInfo.originalClass = creatorInfo.originalClass; | ||
selfClassInfo.computedGetters = creatorClassInfo.computedGetters; | ||
selfClassInfo.ignoreState = creatorClassInfo.ignoreState; | ||
connect_Connect.setupConnectedProps(component, selfClassInfo, creator, creatorClassInfo); | ||
selfInfo.dispatch = store.dispatch; | ||
selfInfo.reducerCreator = reducer_ComponentReducer.createReducer(component, creator); | ||
var templateClassInfo = classInfo_ClassInfo.getInfo(template) || new classInfo_ClassInfo(); | ||
selfClassInfo.ignoreState = templateClassInfo.ignoreState; | ||
selfInfo.reducerCreator = reducer_ComponentReducer.createReducer(component, template); | ||
}; | ||
Component.createSubComponents = function (obj, store, creator, context) { | ||
if (isPrimitive(obj)) | ||
Component.createSubComponents = function (treeNode, store, template, context) { | ||
if (isPrimitive(treeNode)) | ||
return; | ||
if (context.visited.has(obj)) | ||
if (context.visitedNodes.has(treeNode)) | ||
return; | ||
context.visited.add(obj); | ||
var searchIn = creator || obj; | ||
context.visitedNodes.add(treeNode); | ||
var searchIn = template || treeNode; | ||
for (var _i = 0, _a = Object.keys(searchIn); _i < _a.length; _i++) { | ||
var key = _a[_i]; | ||
var connectionInfo = connect_Connect.isConnectedProperty(obj, key); | ||
if (connectionInfo) | ||
continue; | ||
var subPath = context.path + '.' + key; | ||
var subCreator = searchIn[key]; | ||
if (creatorInfo_CreatorInfo.getInfo(subCreator)) { | ||
obj[key] = Component.create(store, subCreator, component___assign({}, context, { parentCreator: creator, path: subPath })); | ||
var subTemplate = searchIn[key]; | ||
if (componentTemplateInfo_ComponentTemplateInfo.getInfo(subTemplate)) { | ||
if (context.visitedTemplates.has(subTemplate)) { | ||
treeNode[key] = context.visitedTemplates.get(subTemplate); | ||
} | ||
else { | ||
treeNode[key] = Component.create(store, subTemplate, component___assign({}, context, { parentTemplate: template, path: subPath })); | ||
} | ||
} | ||
else { | ||
Component.createSubComponents(obj[key], store, null, component___assign({}, context, { parentCreator: null, path: subPath })); | ||
Component.createSubComponents(treeNode[key], store, null, component___assign({}, context, { parentTemplate: null, path: subPath })); | ||
} | ||
@@ -1362,11 +1112,12 @@ } | ||
var snakecase = __webpack_require__(6); | ||
var snakecase = __webpack_require__(4); | ||
var actions_ComponentActions = (function () { | ||
function ComponentActions() { | ||
} | ||
ComponentActions.createActions = function (creator) { | ||
var methods = getCreatorMethods(creator); | ||
ComponentActions.createActions = function (template) { | ||
var methods = getMethods(template); | ||
if (!methods) | ||
return undefined; | ||
var creatorInfo = creatorInfo_CreatorInfo.getInfo(creator); | ||
var templateInfo = componentTemplateInfo_ComponentTemplateInfo.getInfo(template); | ||
var componentActions = {}; | ||
@@ -1382,6 +1133,6 @@ Object.keys(methods).forEach(function (key) { | ||
var oldMethod = methods[key]; | ||
if (!creatorInfo.noDispatch[key]) { | ||
if (templateInfo.actions[key] || templateInfo.sequences[key]) { | ||
var compInfo = componentInfo_ComponentInfo.getInfo(this); | ||
var action = { | ||
type: ComponentActions.getActionName(creator, key, creatorInfo.options), | ||
type: ComponentActions.getActionName(template, key), | ||
id: (compInfo ? compInfo.id : undefined), | ||
@@ -1392,3 +1143,3 @@ payload: payload | ||
} | ||
if (creatorInfo.noDispatch[key] || creatorInfo.sequence[key]) { | ||
if (!templateInfo.actions[key]) { | ||
return oldMethod.call.apply(oldMethod, [this].concat(payload)); | ||
@@ -1400,6 +1151,6 @@ } | ||
}; | ||
ComponentActions.getActionName = function (creator, methodName, options) { | ||
options = Object.assign(new SchemaOptions(), globalOptions.schema, options); | ||
ComponentActions.getActionName = function (template, methodName) { | ||
var options = Object.assign(new ActionOptions(), globalOptions.action); | ||
var actionName = methodName; | ||
var actionNamespace = creator.constructor.name; | ||
var actionNamespace = template.constructor.name; | ||
if (options.uppercaseActions) { | ||
@@ -1435,16 +1186,12 @@ actionName = snakecase(actionName).toUpperCase(); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "isInstanceOf", function() { return isInstanceOf; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "component", function() { return component_component; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "connect", function() { return connect; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "computed", function() { return computed; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "action", function() { return action_action; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "ignoreState", function() { return ignoreState; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "noDispatch", function() { return noDispatch; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "sequence", function() { return sequence; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "withId", function() { return withId; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "SchemaOptions", function() { return SchemaOptions; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "ComputedOptions", function() { return ComputedOptions; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "ActionOptions", function() { return ActionOptions; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "AppOptions", function() { return AppOptions; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "GlobalOptions", function() { return GlobalOptions; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "LogLevel", function() { return LogLevel; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "getMethods", function() { return getMethods; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "ReduxApp", function() { return reduxApp_ReduxApp; }); | ||
/* concated harmony reexport */__webpack_require__.d(__webpack_exports__, "getMethods", function() { return getMethods; }); | ||
@@ -1473,14 +1220,2 @@ | ||
module.exports = require("reflect-metadata"); | ||
/***/ }), | ||
/* 5 */ | ||
/***/ (function(module, exports) { | ||
module.exports = require("lodash.isequal"); | ||
/***/ }), | ||
/* 6 */ | ||
/***/ (function(module, exports) { | ||
module.exports = require("lodash.snakecase"); | ||
@@ -1487,0 +1222,0 @@ |
@@ -1,1 +0,1 @@ | ||
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define("redux-app",[],t):"object"==typeof exports?exports["redux-app"]=t():e["redux-app"]=t()}("undefined"!=typeof self?self:this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return e[r].call(o.exports,o,o.exports,t),o.l=!0,o.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="",t(t.s=0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){return"symbol"==typeof e||e instanceof Symbol}function o(e,t,n){return e[t]=n}function i(e,t){return e[t]}function c(e,t){return Object.getOwnPropertySymbols(e).includes(t)&&i(e,t)}function a(e,t,n){var r=function(e){return function(r){if(Object.defineProperty(this,t,n),e)return this[t];this[t]=r}};return Object.defineProperty(e,t,{get:r(!0),set:r(!1),enumerable:!0,configurable:!0})}function s(e){var t=Object.keys(e);return function(n,r){void 0===n&&(n={});for(var o=!1,i={},c=0,a=t;c<a.length;c++){var s=a[c],u=e[s],p=n[s],f=u(p,r);i[s]=f,o=o||f!==p}return o?i:n}}function u(e){if(!e)return!0;var t=typeof e;return"object"!==t&&"function"!==t}function p(e,t){if(void 0===t&&(t=!1),e){var n;if("object"==typeof e)n=Object.getPrototypeOf(e);else{if("function"!=typeof e)throw new Error("Expected an object or a function. Got: "+e);n=e.prototype}if(n){for(var r={},o=0,i=Object.keys(n);o<i.length;o++){var c=i[o],a=Object.getOwnPropertyDescriptor(n,c);a&&"function"==typeof a.get||"function"!=typeof n[c]||(r[c]=n[c],t&&(r[c]=r[c].bind(e)))}return r}}}function f(e,t){if(e&&e.constructor){var n=e.constructor;return r(t)&&Object.getOwnPropertySymbols(n).includes(t)?n[t]:"string"==typeof t&&Object.getOwnPropertyNames(n).includes(t)?n[t]:void 0}}function d(e){if(e){if("function"==typeof e)return e;if("object"==typeof e)return Object.getPrototypeOf(e).constructor;throw new Error("Expected an object or a function. Got: "+e)}}function h(e){var t=d(e);return Object.getPrototypeOf(t.prototype).constructor}function l(e){var t=JSON.stringify(e,function(e,t){return void 0===t?null:t});return JSON.parse(t)}function g(e){for(var t=Object.keys(e),n=0,r=t;n<r.length;n++){delete e[r[n]]}}function v(e,t){if(void 0===t&&(t=!0),V.getInfo(e)){var n=p(e);if(t)for(var r=h(e);r!==Object;){var o=v(r,!1);n=Object.assign({},o,n),r=h(r)}return n}}function y(e,t){if(!t)return function(t,n){return m(t,n,e)};m.call(void 0,e,t)}function m(e,t,n){n=Object.assign(new ee,n);var r=e[t];N.getOrInitInfo(e).connectedProps[t]=!0;var o=Reflect.getMetadata("design:type",e,t);if(!o){var i="[connect] Failed to reflect type of property '"+t+"'. Make sure you're using TypeScript and that the 'emitDecoratorMetadata' compiler option in your tsconfig.json file is turned on. Note that even if TypeScript is configured correctly it may fail to reflect property types due to the loading order of your classes. In that case, make sure that the type of '"+t+"' is loaded prior to the type of it's containing class ("+e.constructor.name+").";throw new Error(i)}var c=Object.getOwnPropertyDescriptor(e,t),s={get:function(){var e=$.getComponent(o,n.id,n.app);return e&&!n.live&&(Object.defineProperty(this,t,L),r=this[t]=e,z.verbose("[connect] Property '"+t+"' connected. Type: "+o.name+".")),e},set:function(e){return Q[n.app]&&z.warn("[connect] Connected component '"+t+"' value assigned. Component disconnected."),c&&c.set?c.set(e):!c||c&&c.writable?r=e:void 0}};return a(e,t,Object.assign({},T,s))}function b(e){if("function"!=typeof e)return function(t){return O(t,e)};O.call(void 0,e)}function O(e,t){V.getOrInitInfo(e).options=t}function j(e,t){var n=Object.getOwnPropertyDescriptor(e,t);if("function"!=typeof n.get)throw new Error("Failed to decorate '"+t+"'. The 'computed' decorator should only be used on getters.");if(n.set)throw new Error("Failed to decorate '"+t+"'. Decorated property should not have a setter.");return N.getOrInitInfo(e).computedGetters[t]=n.get,a(e,t,L)}function w(e,t){N.getOrInitInfo(e).ignoreState[t]=!0}function C(e,t){V.getOrInitInfo(e).noDispatch[t]=!0}function P(e,t){V.getOrInitInfo(e).sequence[t]=!0}function I(e,t){if(!t)return function(t,n){return S(t,n,e)};S.call(void 0,e,t)}function S(e,t,n){V.getOrInitInfo(e).childIds[t]=n||_}function A(e,t){if(e instanceof t)return!0;var n=E.getInfo(e);return!(!n||n.originalClass!==t)}Object.defineProperty(t,"__esModule",{value:!0});var R,x=Symbol("REDUX-APP.COMPONENT_INFO"),k=Symbol("REDUX-APP.CREATOR_INFO"),D=Symbol("REDUX-APP.CLASS_INFO"),_=Symbol("REDUX-APP.AUTO_ID"),N=function(){function e(){this.computedGetters={},this.connectedProps={},this.ignoreState={}}return e.getInfo=function(e){if(e)return i(e,D)},e.getOrInitInfo=function(t){var n=e.getInfo(t);return n||(n=o(t,D,new e)),n},e}(),E=function(){function e(){}return e.getInfo=function(e){if(e)return i(e,x)},e.initInfo=function(t){return o(t,x,new e)},e}(),L={writable:!0,configurable:!0,enumerable:!0},T={configurable:!0,enumerable:!0},G=function(){function e(){this.actionNamespace=!0,this.actionNamespaceSeparator=".",this.uppercaseActions=!0}return e}(),M=function(){function e(){this.deepComparison=!0}return e}(),q=function(){function e(){this.updateState=!0}return e}();!function(e){e[e.None=0]="None",e[e.Verbose=1]="Verbose",e[e.Debug=2]="Debug",e[e.Warn=5]="Warn",e[e.Silent=10]="Silent"}(R||(R={}));var U=function(){function e(){this.logLevel=R.Warn,this.emitClassNames=!1,this.convertToPlainObject=!0,this.schema=new G,this.computed=new M}return e}(),W=new U,F=function(){function e(){}return e.prototype.verbose=function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];this.shouldLog(R.Verbose)&&console.debug.apply(console,["VERBOSE [redux-app] "+e].concat(t))},e.prototype.debug=function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];this.shouldLog(R.Debug)&&console.log.apply(console,["DEBUG [redux-app] "+e].concat(t))},e.prototype.warn=function(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];this.shouldLog(R.Warn)&&console.warn.apply(console,["WARN [redux-app] "+e].concat(t))},e.prototype.shouldLog=function(e){return W.logLevel!==R.None&&!(W.logLevel>e)},e}(),z=new F,V=function(){function e(){this.noDispatch={},this.sequence={},this.childIds={}}return e.getInfo=function(e){if(e)return"object"==typeof e?f(e,k):c(e,k)},e.getOrInitInfo=function(t){var n=e.getInfo(t);if(!n){var r="function"==typeof t,c=r?t:t.constructor,a=i(c,k),s=Object.assign(new e,a);n=o(c,k,s)}return n},e}(),X=n(2),B=this&&this.__assign||Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++){t=arguments[n];for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o])}return e},J=n(3),H="root",K="default",Q={},Y=0,Z=function(){function e(e){this.visited=new Set,this.path=H,this.forceRecursion=!1,Object.assign(this,e)}return e}(),$=function(){function e(e){for(var t=[],n=1;n<arguments.length;n++)t[n-1]=arguments[n];this.warehouse=new Map,this.initialStateUpdated=!1;var r=this.resolveParameters(e,t),o=r.options,i=r.preLoadedState,c=r.enhancer;if(this.name=this.getAppName(o.name),Q[this.name])throw new Error("An app with name '"+this.name+"' already exists.");Q[this.name]=this;var a=function(e){return e};this.store=Object(X.createStore)(a,i,c);var s=new fe({appName:this.name}),u=de.create(this.store,e,s);this.root=u;var p=new ae({componentPaths:Object.keys(s.createdComponents)}),f=se.combineReducersTree(this.root,p);if(o.updateState){var d=this.updateState(s.createdComponents,p);this.subscriptionDisposer=this.store.subscribe(d)}this.store.replaceReducer(f)}return e.createApp=function(t){for(var n=[],r=1;r<arguments.length;r++)n[r-1]=arguments[r];return new(e.bind.apply(e,[void 0,t].concat(n)))},e.getApp=function(e){var t=e||K,n=Q[t];return n||z.debug("[ReduxApp] Application '"+t+"' does not exist."),n},e.getComponent=function(t,n,r){var o=e.getApp(r);if(o){var i=o.getTypeWarehouse(t);return n?i.get(n):i.values().next().value}},e.registerComponent=function(e,t,n){n=n||K;var r=Q[n];if(r){var o=r.getTypeWarehouse(t.constructor),i=E.getInfo(e).id||ie.nextAvailableId();o.set(i,e)}},e.prototype.dispose=function(){this.subscriptionDisposer&&(this.subscriptionDisposer(),this.subscriptionDisposer=null),Q[this.name]&&delete Q[this.name]},e.prototype.getTypeWarehouse=function(e){return this.warehouse.has(e)||this.warehouse.set(e,new Map),this.warehouse.get(e)},e.prototype.resolveParameters=function(e,t){var n={};return 0===t.length?(n.options=new q,n.preLoadedState=e):1===t.length?"function"==typeof t[0]?(n.options=new q,n.enhancer=t[0],n.preLoadedState=e):(n.options=Object.assign(new q,t[0]),n.preLoadedState=e):2===t.length?(n.options=Object.assign(new q,t[0]),n.preLoadedState=t[1]):(n.options=Object.assign(new q,t[0]),n.preLoadedState=t[1],n.enhancer=t[2]),n},e.prototype.getAppName=function(e){return e||(Object.keys(Q).length?K+"_"+ ++Y:K)},e.prototype.updateState=function(e,t){var n=this,r=re.filterComponents(Object.values(e));return function(){var e=Date.now(),o=n.store.getState();n.initialStateUpdated&&t.invoked?n.updateChangedComponents((c={},c[H]=o,c),t.changedComponents):(n.initialStateUpdated=!0,n.updateStateRecursion(n.root,o,new Z({forceRecursion:!0}))),re.computeProps(r),t.reset();var i=Date.now();z.debug("[updateState] Component tree updated in "+(i-e)+"ms.");var c}},e.prototype.updateChangedComponents=function(e,t){for(var n=Object.keys(t),r=new Z,o=0,i=n;o<i.length;o++){var c=i[o],a=t[c],s=J(e,c);this.updateStateRecursion(a,s,B({},r,{path:c}))}},e.prototype.updateStateRecursion=function(e,t,n){if(e===t)return t;if(u(e)||u(t))return t;if(n.visited.has(e))return e;n.visited.add(e);var r=t.constructor;if(W.convertToPlainObject&&(t=l(t)),n.forceRecursion||e instanceof de&&r===Object){var o;o=Array.isArray(e)&&Array.isArray(t)?this.updateArray(e,t,n):this.updateObject(e,t,n)}else e=t,o="Object overwritten.";return o&&o.length&&(z.debug("[updateState] Change in '"+n.path+"'. "+o),z.verbose("[updateState] New state: ",e)),e},e.prototype.updateObject=function(e,t,n){var r=this,o=[];Object.keys(e).forEach(function(r){oe.isIgnoredProperty(e,r)||t.hasOwnProperty(r)||("function"==typeof e[r]&&z.warn("[updateState] Function property removed in path: "+n.path+"."+r+". Consider using a method instead."),delete e[r],o.push(r))});var i=[];if(Object.keys(t).forEach(function(o){if(!te.isConnectedProperty(e,o)&&!re.isComputedProperty(e,o)&&!oe.isIgnoredProperty(e,o)){var c=t[o],a=e[o],s=r.updateStateRecursion(a,c,B({},n,{path:n.path+"."+o}));s!==a&&(e[o]=s,i.push(o))}}),i.length||o.length){var c=i.length?"Props assigned: "+i.join(", ")+".":"",a=o.length?"Props deleted: "+o.join(", ")+".":"";return c+(i.length&&o.length?" ":"")+a}return null},e.prototype.updateArray=function(e,t,n){for(var r=[],o=e.length,i=t.length,c=[],a=0;a<Math.min(o,i);a++){var s=t[a],u=e[a],p=this.updateStateRecursion(u,s,B({},n,{path:n.path+"."+a}));p!==u&&(e[a]=p,c.push(a))}if(c.length&&r.push("Assigned item(s) at indexes "+c.join(", ")+"."),i>o){var f=t.slice(o);Array.prototype.push.apply(e,f),r.push("Added "+(i-o)+" item(s) at index "+o+".")}else o>i&&(e.splice(i),r.push("Removed "+(o-i)+" item(s) at index "+i+"."));return r.join(" ")},e.options=W,e}(),ee=function(){function e(){this.app=K,this.live=!1}return e}(),te=function(){function e(){}return e.isConnectedProperty=function(e,t){var n=N.getInfo(e);return n&&n.connectedProps[t]},e.setupConnectedProps=function(e,t,n,r){if(r){for(var o=0,i=Object.keys(r.connectedProps);o<i.length;o++){var c=i[o];n[c];var a=Object.getOwnPropertyDescriptor(n,c);Object.defineProperty(e,c,a)}t.connectedProps=r.connectedProps}},e.removeConnectedProps=function(t,n,r){var o=N.getInfo(n);if(!o)return t;Object.assign(r,o.connectedProps);for(var i=Object.assign({},t),c=0,a=Object.keys(o.connectedProps);c<a.length;c++){var s=a[c],u="",p=E.getInfo(n[s]);if(p){var f=void 0!==p.id?"."+p.id:"";u="."+p.originalClass.name+f}i[s]=e.placeholderPrefix+u+">"}return i},e.placeholderPrefix="<connected",e}(),ne=(n(4),n(5)),re=function(){function e(){}return e.isComputedProperty=function(e,t){var n=N.getInfo(e);return n&&"function"==typeof n.computedGetters[t]},e.removeComputedProps=function(t,n,r){var o=N.getInfo(n);if(!o)return t;Object.assign(r,o.computedGetters);for(var i=Object.assign({},t),c=0,a=Object.keys(o.computedGetters);c<a.length;c++){i[a[c]]=e.placeholder}return i},e.filterComponents=function(e){return e.filter(function(e){var t=N.getInfo(e);return t&&Object.keys(t.computedGetters)})},e.computeProps=function(e){for(var t=0,n=e;t<n.length;t++){var r=n[t];this.computeObjectProps(r)}},e.computeObjectProps=function(e){var t=N.getInfo(e);if(t)for(var n=0,r=Object.keys(t.computedGetters);n<r.length;n++){var o=r[n],i=t.computedGetters[o],c=i.call(e),a=e[o];c===a||W.computed.deepComparison&&ne(c,a)||(e[o]=c)}},e.placeholder="<computed>",e}(),oe=function(){function e(){}return e.isIgnoredProperty=function(e,t){var n=N.getInfo(e);return n&&n.ignoreState[t]},e.removeIgnoredProps=function(e,t,n){var r=N.getInfo(t);if(!r)return e;Object.assign(n,r.ignoreState);for(var o=Object.assign({},e),i=0,c=Object.keys(r.ignoreState);i<c.length;i++){delete o[c[i]]}return o},e}(),ie=function(){function e(){}return e.nextAvailableId=function(){return--e.autoComponentId},e.getComponentId=function(t,n){var r=n.split(".");if(t&&r.length){var o=V.getInfo(t);if(o){var i=r[r.length-1],c=o.childIds[i];if(c){if(c===_){var a=e.nextAvailableId();return z.verbose("[getComponentId] new component id generated: "+a),o.childIds[i]=a,a}return c}}}},e.autoComponentId=0,e}(),ce=this&&this.__assign||Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++){t=arguments[n];for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o])}return e},ae=function(){function e(e){this.visited=new Set,this.path=H,this.componentPaths=[],this.changedComponents={},this.invoked=!1,Object.assign(this,e)}return e.prototype.reset=function(){g(this.changedComponents),this.invoked=!1},e}(),se=function(){function e(){}return e.createReducer=function(t,n){var r=V.getInfo(n);if(!r)throw new Error("Inconsistent component '"+n.constructor.name+"'. The 'component' class decorator is missing.");var o=e.createMethodsLookup(n,r),i=e.createStateObjectPrototype(t,r),c=E.getInfo(t).id;return function(r){return function(a,s){if(z.verbose("[reducer] Reducer of: "+n.constructor.name+", action: "+s.type),void 0===a)return z.verbose("[reducer] State is undefined, returning initial value"),t;if(c!==s.id)return z.verbose("[reducer] Component id and action.id don't match ("+c+" !== "+s.id+")"),a;var u=o[s.type];if(!u)return z.verbose("[reducer] No matching action in this reducer, returning previous state"),a;var p=e.createStateObject(a,i);return u.call.apply(u,[p].concat(s.payload)),r(t),z.verbose("[reducer] Reducer invoked, returning new state"),p}}},e.combineReducersTree=function(t,n){var r=e.combineReducersRecursion(t,n);return function(o,i){var c=Date.now();n.invoked=!0,z.debug("[rootReducer] Reducing action: "+i.type+".");var a=r(o,i);a=e.finalizeState(a,t);var s=Date.now();return z.debug("[rootReducer] Reducer tree processed in "+(s-c)+"ms."),a}},e.createMethodsLookup=function(e,t){var n=v(e),r=t.options,o={};return Object.keys(n).forEach(function(i){if(!t.noDispatch[i]&&!t.sequence[i]){var c=le.getActionName(e,i,r);o[c]=n[i]}}),o},e.createStateObjectPrototype=function(t,n){for(var r={},o=p(t),i=0,c=Object.keys(o);i<c.length;i++){var a=c[i];n.noDispatch[a]?r[a]=o[a].bind(t):r[a]=e.actionInvokedError}return r},e.actionInvokedError=function(){throw new Error("Only 'noDispatch' methods can be invoked inside actions.")},e.createStateObject=function(e,t){var n=Object.create(t);return Object.assign(n,e),n},e.combineReducersRecursion=function(t,n){if(!u(t)&&!n.visited.has(t)){if(n.visited.add(t),!n.componentPaths.some(function(e){return e.startsWith(n.path)}))return e.identityReducer;var r,o=E.getInfo(t);r=o?o.reducerCreator(function(e){n.changedComponents[n.path]=e}):e.identityReducer;for(var i={},c=0,a=Object.keys(t);c<a.length;c++){var p=a[c];if(!te.isConnectedProperty(t,p)){var f=e.combineReducersRecursion(t[p],new ae(ce({},n,{path:""===n.path?p:n.path+"."+p})));"function"==typeof f&&(i[p]=f)}}var d=r;if(Object.keys(i).length){var h=s(i);d=function(t,n){var o=r(t,n),i=h(o,n);return e.mergeState(o,i)}}return d}},e.mergeState=function(e,t){if(Array.isArray(e)&&Array.isArray(t)){for(var n=0;n<t.length;n++)e[n]=t[n];return e}return ce({},e,t)},e.finalizeState=function(t,n){return e.finalizeStateRecursion(t,n,new Set)},e.finalizeStateRecursion=function(t,n,r){if(u(t)||u(n))return t;if(r.has(t))return t;r.add(t);var o={};return t=te.removeConnectedProps(t,n,o),t=re.removeComputedProps(t,n,o),t=oe.removeIgnoredProps(t,n,o),Object.keys(t).forEach(function(i){if(!o.hasOwnProperty(i)){var c=t[i],a=n[i],s=e.finalizeStateRecursion(c,a,r);s!==c&&(t[i]=s)}}),t},e.identityReducer=function(e){return e},e}(),ue=this&&this.__extends||function(){var e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])};return function(t,n){function r(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(r.prototype=n.prototype,new r)}}(),pe=this&&this.__assign||Object.assign||function(e){for(var t,n=1,r=arguments.length;n<r;n++){t=arguments[n];for(var o in t)Object.prototype.hasOwnProperty.call(t,o)&&(e[o]=t[o])}return e},fe=function(){function e(e){this.visited=new Set,this.path=H,this.createdComponents={},Object.assign(this,e)}return e}(),de=function(){function e(t,n,r){if(!V.getInfo(n))throw new Error("Argument 'creator' is not a component creator. Did you forget to use the decorator?");e.createSelf(this,t,n,r),e.createSubComponents(this,t,n,r),r.createdComponents[r.path]=this,z.verbose("[Component] New "+n.constructor.name+" component created. Path: "+r.path)}return e.create=function(t,n,r){r=Object.assign(new fe,r);var o=e.getComponentClass(n),i=new o(t,n,r);return $.registerComponent(i,n,r.appName),i},e.getComponentClass=function(t){var n=V.getInfo(t);return n.componentClass||(n.componentClass=e.createComponentClass(t),n.originalClass=t.constructor),n.componentClass},e.createComponentClass=function(t){var n=function(e){function n(n,r,o){var i=e.call(this,n,r,o)||this;return i.__originalClassName__=t.constructor.name,W.emitClassNames||delete i.__originalClassName__,i}return ue(n,e),n}(e),r=le.createActions(t);return Object.assign(n.prototype,r),n},e.createSelf=function(e,t,n,r){for(var o=0,i=Object.getOwnPropertyNames(n);o<i.length;o++){var c=i[o],a=Object.getOwnPropertyDescriptor(n,c);Object.defineProperty(e,c,a)}var s=E.initInfo(e),u=N.getOrInitInfo(e),p=V.getInfo(n),f=N.getInfo(n)||new N;s.id=ie.getComponentId(r.parentCreator,r.path),s.originalClass=p.originalClass,u.computedGetters=f.computedGetters,u.ignoreState=f.ignoreState,te.setupConnectedProps(e,u,n,f),s.dispatch=t.dispatch,s.reducerCreator=se.createReducer(e,n)},e.createSubComponents=function(t,n,r,o){if(!u(t)&&!o.visited.has(t)){o.visited.add(t);for(var i=r||t,c=0,a=Object.keys(i);c<a.length;c++){var s=a[c];if(!te.isConnectedProperty(t,s)){var p=o.path+"."+s,f=i[s];V.getInfo(f)?t[s]=e.create(n,f,pe({},o,{parentCreator:r,path:p})):e.createSubComponents(t[s],n,null,pe({},o,{parentCreator:null,path:p}))}}}},e}(),he=n(6),le=function(){function e(){}return e.createActions=function(t){var n=v(t);if(n){var r=V.getInfo(t),o={};return Object.keys(n).forEach(function(i){o[i]=function(){for(var o=[],c=0;c<arguments.length;c++)o[c]=arguments[c];if(!(this instanceof de))throw new Error("Component method invoked with non-Component as 'this'. Bound 'this' argument is: "+this);var a=n[i];if(!r.noDispatch[i]){var s=E.getInfo(this),u={type:e.getActionName(t,i,r.options),id:s?s.id:void 0,payload:o};s.dispatch(u)}if(r.noDispatch[i]||r.sequence[i])return a.call.apply(a,[this].concat(o))}}),o}},e.getActionName=function(e,t,n){n=Object.assign(new G,W.schema,n);var r=t,o=e.constructor.name;return n.uppercaseActions&&(r=he(r).toUpperCase(),o=he(o).toUpperCase()),n.actionNamespace&&(r=o+n.actionNamespaceSeparator+r),r},e}();n.d(t,"isInstanceOf",function(){return A}),n.d(t,"component",function(){return b}),n.d(t,"connect",function(){return y}),n.d(t,"computed",function(){return j}),n.d(t,"ignoreState",function(){return w}),n.d(t,"noDispatch",function(){return C}),n.d(t,"sequence",function(){return P}),n.d(t,"withId",function(){return I}),n.d(t,"SchemaOptions",function(){return G}),n.d(t,"ComputedOptions",function(){return M}),n.d(t,"AppOptions",function(){return q}),n.d(t,"GlobalOptions",function(){return U}),n.d(t,"LogLevel",function(){return R}),n.d(t,"ReduxApp",function(){return $}),n.d(t,"getMethods",function(){return p})},function(e,t){e.exports=require("redux")},function(e,t){e.exports=require("lodash.get")},function(e,t){e.exports=require("reflect-metadata")},function(e,t){e.exports=require("lodash.isequal")},function(e,t){e.exports=require("lodash.snakecase")}])}); | ||
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define("redux-app",[],e):"object"==typeof exports?exports["redux-app"]=e():t["redux-app"]=e()}("undefined"!=typeof self?self:this,function(){return function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=0)}([function(t,e,n){t.exports=n(1)},function(t,e,n){"use strict";function r(t){return"symbol"==typeof t||t instanceof Symbol}function o(t,e,n){return t[e]=n}function i(t,e){return t[e]}function a(t,e){return Object.getOwnPropertySymbols(t).includes(e)&&i(t,e)}function s(t){var e=Object.keys(t);return function(n,r){void 0===n&&(n={});for(var o=!1,i={},a=0,s=e;a<s.length;a++){var c=s[a],u=t[c],p=n[c],f=u(p,r);i[c]=f,o=o||f!==p}return o?i:n}}function c(t){for(var e=Object.keys(t),n=0,r=e;n<r.length;n++){delete t[r[n]]}}function u(t,e,n){for(var r=p(e,n),o=0,i=Object.keys(r);o<i.length;o++){var a=i[o];Object.defineProperty(t,a,r[a])}return t}function p(t,e){for(var n={};t.constructor!==Object;){var r=Object.getOwnPropertyDescriptors(t);if(e&&e.length){for(var o={},i=0,a=Object.keys(r);i<a.length;i++)for(var s=a[i],c=0,u=e;c<u.length;c++){var p=u[c],f=!1;switch(p){case N.None:break;case N.Field:f="function"!=typeof r[s].value&&"function"!=typeof r[s].get;break;case N.Property:f="function"==typeof r[s].get;break;case N.Method:f="function"==typeof r[s].value&&"function"!=typeof r[s].get;break;default:throw new Error("Property flag not supported: "+p)}f&&(o[s]=r[s])}r=o}n=Object.assign(r,n),t=l(t)}return n.constructor&&delete n.constructor,n}function f(t,e){if(t&&t.constructor){return t.constructor[e]}}function d(t,e){if(t&&t.constructor){var n=t.constructor;return r(e)&&Object.getOwnPropertySymbols(n).includes(e)?n[e]:"string"==typeof e&&Object.getOwnPropertyNames(n).includes(e)?n[e]:void 0}}function h(t,e){void 0===e&&(e=!1);for(var n=p(t,[N.Method]),r={},o=0,i=Object.keys(n);o<i.length;o++){var a=i[o];r[a]=n[a].value,e&&(r[a]=r[a].bind(t))}return r}function l(t){if("object"==typeof t)return Object.getPrototypeOf(t);if("function"==typeof t)return t.prototype;throw new Error("Expected an object or a function. Got: "+t)}function g(t){if(!t)return!0;var e=typeof t;return"object"!==e&&"function"!==e}function v(t,e){_.getOrInitInfo(t).actions[e]=!0}function y(t,e){A.getOrInitInfo(t).ignoreState[e]=!0}function b(t,e){_.getOrInitInfo(t).sequences[e]=!0}function m(t,e){if(!e)return function(e,n){return O(e,n,t)};O.call(void 0,t,e)}function O(t,e,n){_.getOrInitInfo(t).childIds[e]=n||P}function w(t,e){if(t instanceof e)return!0;var n=R.getInfo(t);return!(!n||n.originalClass!==e)}Object.defineProperty(e,"__esModule",{value:!0});var j,I=Symbol("REDUX-APP.COMPONENT_INFO"),S=Symbol("REDUX-APP.COMPONENT_TEMPLATE_INFO"),C=Symbol("REDUX-APP.CLASS_INFO"),P=Symbol("REDUX-APP.AUTO_ID"),A=function(){function t(){this.ignoreState={}}return t.getInfo=function(t){if(t)return i(t,C)},t.getOrInitInfo=function(e){var n=t.getInfo(e);return n||(n=o(e,C,new t)),n},t}(),R=function(){function t(t,e,n){this.originalClass=t.constructor,this.dispatch=e,this.id=n}return t.getInfo=function(t){if(t)return i(t,I)},t.initInfo=function(e,n,r,i){var a=new t(n,r,i);return o(e,I,a)},t}(),k=function(){function t(){this.actionNamespace=!0,this.actionNamespaceSeparator=".",this.uppercaseActions=!1}return t}(),x=function(){function t(){this.updateState=!0}return t}();!function(t){t[t.None=0]="None",t[t.Verbose=1]="Verbose",t[t.Debug=2]="Debug",t[t.Warn=5]="Warn",t[t.Silent=10]="Silent"}(j||(j={}));var N,E=function(){function t(){this.logLevel=j.Warn,this.action=new k}return t}(),D=new E,T=function(){function t(){}return t.prototype.verbose=function(t){for(var e=[],n=1;n<arguments.length;n++)e[n-1]=arguments[n];this.shouldLog(j.Verbose)&&console.debug.apply(console,["VERBOSE [redux-app] "+t].concat(e))},t.prototype.debug=function(t){for(var e=[],n=1;n<arguments.length;n++)e[n-1]=arguments[n];this.shouldLog(j.Debug)&&console.log.apply(console,["DEBUG [redux-app] "+t].concat(e))},t.prototype.warn=function(t){for(var e=[],n=1;n<arguments.length;n++)e[n-1]=arguments[n];this.shouldLog(j.Warn)&&console.warn.apply(console,["WARN [redux-app] "+t].concat(e))},t.prototype.shouldLog=function(t){return D.logLevel!==j.None&&!(D.logLevel>t)},t}(),L=new T;!function(t){t.None="None",t.Field="Field",t.Property="Property",t.Method="Method"}(N||(N={}));var _=function(){function t(){this.actions={},this.sequences={},this.childIds={}}return t.getInfo=function(e){if(e){var n=t.getOwnInfo(e);if(n)return n;return t.getBaseInfo(e)?t.initInfo(e):void 0}},t.getOrInitInfo=function(e){var n=t.getInfo(e);return n||t.initInfo(e)},t.getOwnInfo=function(t){return"object"==typeof t?d(t,S):a(t,S)},t.getBaseInfo=function(t){return"object"==typeof t?f(t,S):i(t,S)},t.initInfo=function(e){var n="function"==typeof e,r=n?e:e.constructor,a=i(r,S),s=Object.assign(new t,a);return o(r,S,s)},t}(),M=function(){function t(){}return t.isIgnoredProperty=function(t,e){var n=A.getInfo(t);return n&&n.ignoreState[e]},t.removeIgnoredProps=function(t,e){var n=A.getInfo(e);if(!n)return t;for(var r=0,o=Object.keys(n.ignoreState);r<o.length;r++){delete t[o[r]]}return t},t}(),U=function(){function t(){}return t.nextAvailableId=function(){return--t.autoComponentId},t.getComponentId=function(e,n){var r=n.split(".");if(e&&r.length){var o=_.getInfo(e);if(o){var i=r[r.length-1],a=o.childIds[i];if(a){if(a===P){var s=t.nextAvailableId();return L.verbose("[getComponentId] new component id generated: "+s),o.childIds[i]=s,s}return a}}}},t.autoComponentId=0,t}(),F=n(2),W=this&&this.__assign||Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++){e=arguments[n];for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&(t[o]=e[o])}return t},q=n(3),z="root",B={},V=0,X=function(){function t(t){this.visited=new Set,this.path=z,this.forceRecursion=!1,Object.assign(this,t)}return t}(),G=function(){function t(t){for(var e=[],n=1;n<arguments.length;n++)e[n-1]=arguments[n];this.warehouse=new Map,this.initialStateUpdated=!1;var r=this.resolveParameters(t,e),o=r.options,i=r.preLoadedState,a=r.enhancer;if(this.name=this.getAppName(o.name),B[this.name])throw new Error("An app with name '"+this.name+"' already exists.");B[this.name]=this;var s=function(t){return t};this.store=Object(F.createStore)(s,i,a);var c=new Y({appName:this.name}),u=Z.create(this.store,t,c);this.root=u,this.registerComponents(c.createdComponents);var p=new J({componentPaths:Object.keys(c.createdComponents)}),f=K.combineReducersTree(this.root,p);if(o.updateState){var d=this.updateState(p);this.subscriptionDisposer=this.store.subscribe(d)}this.store.replaceReducer(f)}return t.createApp=function(e){for(var n=[],r=1;r<arguments.length;r++)n[r-1]=arguments[r];return new(t.bind.apply(t,[void 0,e].concat(n)))},t.getComponent=function(e,n,r){var o=t.getApp(r);if(!o)throw new Error("App not found (id: '"+(r||"default")+"')");var i=o.getTypeWarehouse(e);if(n){var a=i.get(n);if(!a)throw new Error("Component not found. Type: "+e.name+". Id: '"+n+"'.");return a}var a=i.values().next().value;if(!a)throw new Error("Component not found. Type: "+e.name+".");return a},t.getApp=function(t){var e=t||"default",n=B[e];return n||L.debug("[ReduxApp] Application '"+e+"' does not exist."),n},t.prototype.dispose=function(){this.subscriptionDisposer&&(this.subscriptionDisposer(),this.subscriptionDisposer=null),B[this.name]&&delete B[this.name]},t.prototype.resolveParameters=function(t,e){var n={};return 0===e.length?(n.options=new x,n.preLoadedState=t):1===e.length?"function"==typeof e[0]?(n.options=new x,n.enhancer=e[0],n.preLoadedState=t):(n.options=Object.assign(new x,e[0]),n.preLoadedState=t):2===e.length?(n.options=Object.assign(new x,e[0]),n.preLoadedState=e[1]):(n.options=Object.assign(new x,e[0]),n.preLoadedState=e[1],n.enhancer=e[2]),n},t.prototype.getAppName=function(t){return t||(Object.keys(B).length?"default_"+ ++V:"default")},t.prototype.registerComponents=function(t){for(var e=0,n=Object.values(t);e<n.length;e++){var r=n[e],o=R.getInfo(r),i=this.getTypeWarehouse(o.originalClass),a=o.id||U.nextAvailableId();i.set(a,r)}},t.prototype.getTypeWarehouse=function(t){return this.warehouse.has(t)||this.warehouse.set(t,new Map),this.warehouse.get(t)},t.prototype.updateState=function(t){var e=this;return function(){var n=Date.now(),r=e.store.getState();e.initialStateUpdated&&t.invoked?e.updateChangedComponents((i={},i[z]=r,i),t.changedComponents):(e.initialStateUpdated=!0,e.updateStateRecursion(e.root,r,new X({forceRecursion:!0}))),t.reset();var o=Date.now();L.debug("[updateState] Component tree updated in "+(o-n)+"ms.");var i}},t.prototype.updateChangedComponents=function(t,e){for(var n=Object.keys(e),r=new X,o=0,i=n;o<i.length;o++){var a=i[o],s=e[a],c=q(t,a);this.updateStateRecursion(s,c,W({},r,{path:a}))}},t.prototype.updateStateRecursion=function(t,e,n){if(t===e)return e;if(g(t)||g(e))return e;if(n.visited.has(t))return t;if(n.visited.add(t),n.forceRecursion||t instanceof Z){var r;r=Array.isArray(t)&&Array.isArray(e)?this.updateArray(t,e,n):this.updateObject(t,e,n)}else t=e,r="Object overwritten.";return r&&r.length&&(L.debug("[updateState] Change in '"+n.path+"'. "+r),L.verbose("[updateState] New state: ",t)),t},t.prototype.updateObject=function(t,e,n){for(var r=[],o=0,i=Object.keys(t);o<i.length;o++){var a=i[o];if(!M.isIgnoredProperty(t,a)&&!e.hasOwnProperty(a)){var s=Object.getOwnPropertyDescriptor(t,a);if(s&&"function"==typeof s.get)continue;"function"==typeof t[a]&&L.warn("[updateState] Function property removed in path: "+n.path+"."+a+". Consider using a method instead."),delete t[a],r.push(a)}}for(var c=[],u=0,p=Object.keys(e);u<p.length;u++){var a=p[u];if(!M.isIgnoredProperty(t,a)){var s=Object.getOwnPropertyDescriptor(t,a);if(!s||"function"!=typeof s.get||"function"==typeof s.set){var f=e[a],d=t[a],h=this.updateStateRecursion(d,f,W({},n,{path:n.path+"."+a}));h!==d&&(t[a]=h,c.push(a))}}}if(c.length||r.length){var l=c.length?"Props assigned: "+c.join(", ")+".":"",g=r.length?"Props deleted: "+r.join(", ")+".":"";return l+(c.length&&r.length?" ":"")+g}return null},t.prototype.updateArray=function(t,e,n){for(var r=[],o=t.length,i=e.length,a=[],s=0;s<Math.min(o,i);s++){var c=e[s],u=t[s],p=this.updateStateRecursion(u,c,W({},n,{path:n.path+"."+s}));p!==u&&(t[s]=p,a.push(s))}if(a.length&&r.push("Assigned item(s) at indexes "+a.join(", ")+"."),i>o){var f=e.slice(o);Array.prototype.push.apply(t,f),r.push("Added "+(i-o)+" item(s) at index "+o+".")}else o>i&&(t.splice(i),r.push("Removed "+(o-i)+" item(s) at index "+i+"."));return r.join(" ")},t.options=D,t}(),H=this&&this.__assign||Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++){e=arguments[n];for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&(t[o]=e[o])}return t},J=function(){function t(t){this.visited=new Set,this.path=z,this.componentPaths=[],this.changedComponents={},this.invoked=!1,Object.assign(this,t)}return t.prototype.reset=function(){c(this.changedComponents),this.invoked=!1},t}(),K=function(){function t(){}return t.createReducer=function(e,n){var r=_.getInfo(n);if(!r)throw new Error("Inconsistent component '"+n.constructor.name+"'. The 'component' class decorator is missing.");var o=t.createMethodsLookup(n,r),i=t.createStateObjectPrototype(e,r),a=R.getInfo(e).id;return function(r){return function(s,c){if(L.verbose("[reducer] Reducer of: "+n.constructor.name+", action: "+c.type+"."),void 0===s)return L.verbose("[reducer] State is undefined, returning initial value."),t.finalizeStateObject(e,e);if(s===n)return L.verbose("[reducer] State equals to component's template, returning initial value."),t.finalizeStateObject(e,e);if(a!==c.id)return L.verbose("[reducer] Component id and action.id don't match ("+a+" !== "+c.id+")."),s;var u=o[c.type];if(!u)return L.verbose("[reducer] No matching action in this reducer, returning previous state."),s;var p=t.createStateObject(s,i);return u.call.apply(u,[p].concat(c.payload)),r(e),L.verbose("[reducer] Reducer invoked, returning new state."),t.finalizeStateObject(p,e)}}},t.combineReducersTree=function(e,n){var r=t.combineReducersRecursion(e,n);return function(t,e){var o=Date.now();n.invoked=!0,L.debug("[rootReducer] Reducing action: "+e.type+".");var i=r(t,e),a=Date.now();return L.debug("[rootReducer] Reducer tree processed in "+(a-o)+"ms."),i}},t.createMethodsLookup=function(t,e){var n=h(t),r={};return Object.keys(e.actions).forEach(function(e){var o=tt.getActionName(t,e);r[o]=n[e]}),r},t.createStateObjectPrototype=function(e,n){for(var r=u({},e,[N.Property]),o=h(e),i=0,a=Object.keys(o);i<a.length;i++){var s=a[i];n.actions[s]?r[s]=t.actionInvokedError:r[s]=o[s].bind(e)}return r},t.actionInvokedError=function(){throw new Error("Actions should not be invoked from within other actions.")},t.createStateObject=function(t,e){for(var n=Object.create(e),r=0,o=Object.keys(t);r<o.length;r++){var i=o[r],a=Object.getOwnPropertyDescriptor(e,i);a&&"function"==typeof a.get&&"function"!=typeof a.set||(n[i]=t[i])}return n},t.finalizeStateObject=function(t,e){L.verbose("[finalizeStateObject] finalizing state.");var n=Object.assign({},t);return n=M.removeIgnoredProps(n,e),L.verbose("[finalizeStateObject] state finalized."),n},t.combineReducersRecursion=function(e,n){if(!g(e)&&!n.visited.has(e)){if(n.visited.add(e),!n.componentPaths.some(function(t){return t.startsWith(n.path)}))return t.identityReducer;var r,o=R.getInfo(e);r=o?o.reducerCreator(function(t){n.changedComponents[n.path]=t}):t.identityReducer;for(var i={},a=0,c=Object.keys(e);a<c.length;a++){var u=c[a],p=t.combineReducersRecursion(e[u],new J(H({},n,{path:""===n.path?u:n.path+"."+u})));"function"==typeof p&&(i[u]=p)}var f=r;if(Object.keys(i).length){var d=s(i);f=function(e,n){var o=r(e,n),i=d(o,n);return t.mergeState(o,i)}}return f}},t.mergeState=function(t,e){if(Array.isArray(t)&&Array.isArray(e)){for(var n=0;n<e.length;n++)t[n]=e[n];return t}return H({},t,e)},t.identityReducer=function(t){return t},t}(),Q=this&&this.__assign||Object.assign||function(t){for(var e,n=1,r=arguments.length;n<r;n++){e=arguments[n];for(var o in e)Object.prototype.hasOwnProperty.call(e,o)&&(t[o]=e[o])}return t},Y=function(){function t(t){this.visitedNodes=new Set,this.visitedTemplates=new Map,this.path=z,this.createdComponents={},Object.assign(this,t)}return t}(),Z=function(){function t(e,n,r){if(!_.getInfo(n))throw new Error("Argument 'template' is not a component template. Did you forget to use the decorator?");t.createSelf(this,e,n,r),r.createdComponents[r.path]=this,r.visitedTemplates.set(n,this),L.verbose("[Component] New "+n.constructor.name+" component created. Path: "+r.path),t.createSubComponents(this,e,n,r)}return t.create=function(e,n,r){return r=Object.assign(new Y,r),_.getOrInitInfo(n),new(t.getComponentClass(n))(e,n,r)},t.getComponentClass=function(e){var n=_.getInfo(e);return n.componentClass||(n.componentClass=t.createComponentClass(e)),n.componentClass},t.createComponentClass=function(e){var n=new Function("initCallback",'"use strict";return function '+e.constructor.name+"_ReduxAppComponent() { initCallback(this, arguments); }"),r=n(function(e,n){return t.apply(e,n)});r.prototype=Object.create(t.prototype),r.prototype.constructor=r;var o=tt.createActions(e);return Object.assign(r.prototype,o),r},t.createSelf=function(t,e,n,r){u(t,n,[N.Field,N.Property]);var o=U.getComponentId(r.parentTemplate,r.path),i=R.initInfo(t,n,e.dispatch,o),a=A.getOrInitInfo(t),s=A.getInfo(n)||new A;a.ignoreState=s.ignoreState,i.reducerCreator=K.createReducer(t,n)},t.createSubComponents=function(e,n,r,o){if(!g(e)&&!o.visitedNodes.has(e)){o.visitedNodes.add(e);for(var i=r||e,a=0,s=Object.keys(i);a<s.length;a++){var c=s[a],u=o.path+"."+c,p=i[c];_.getInfo(p)?o.visitedTemplates.has(p)?e[c]=o.visitedTemplates.get(p):e[c]=t.create(n,p,Q({},o,{parentTemplate:r,path:u})):t.createSubComponents(e[c],n,null,Q({},o,{parentTemplate:null,path:u}))}}},t}(),$=n(4),tt=function(){function t(){}return t.createActions=function(e){var n=h(e);if(n){var r=_.getInfo(e),o={};return Object.keys(n).forEach(function(i){o[i]=function(){for(var o=[],a=0;a<arguments.length;a++)o[a]=arguments[a];if(!(this instanceof Z))throw new Error("Component method invoked with non-Component as 'this'. Bound 'this' argument is: "+this);var s=n[i];if(r.actions[i]||r.sequences[i]){var c=R.getInfo(this),u={type:t.getActionName(e,i),id:c?c.id:void 0,payload:o};c.dispatch(u)}if(!r.actions[i])return s.call.apply(s,[this].concat(o))}}),o}},t.getActionName=function(t,e){var n=Object.assign(new k,D.action),r=e,o=t.constructor.name;return n.uppercaseActions&&(r=$(r).toUpperCase(),o=$(o).toUpperCase()),n.actionNamespace&&(r=o+n.actionNamespaceSeparator+r),r},t}();n.d(e,"isInstanceOf",function(){return w}),n.d(e,"action",function(){return v}),n.d(e,"ignoreState",function(){return y}),n.d(e,"sequence",function(){return b}),n.d(e,"withId",function(){return m}),n.d(e,"ActionOptions",function(){return k}),n.d(e,"AppOptions",function(){return x}),n.d(e,"GlobalOptions",function(){return E}),n.d(e,"LogLevel",function(){return j}),n.d(e,"getMethods",function(){return h}),n.d(e,"ReduxApp",function(){return G})},function(t,e){t.exports=require("redux")},function(t,e){t.exports=require("lodash.get")},function(t,e){t.exports=require("lodash.snakecase")}])}); |
{ | ||
"name": "redux-app", | ||
"version": "1.11.1", | ||
"version": "2.0.0", | ||
"description": "Type-safe, DRY and OO redux. Implemented with typescript.", | ||
@@ -10,3 +10,2 @@ "keywords": [ | ||
"aurelia", | ||
"react", | ||
"dry", | ||
@@ -33,12 +32,13 @@ "object oriented", | ||
"scripts": { | ||
"release": "webpack && webpack --config webpack.config.dev.js", | ||
"dev": "webpack --config webpack.config.dev.js --watch", | ||
"test": "mocha-webpack --watch --require source-map-support/register --webpack-config webpack.config.test.js \"test/**/*.tests.ts\"" | ||
"lint": "npm run lint-lint --silent", | ||
"lint-lint": "tslint \"./{src,test}/**/*.{ts,tsx}\" --exclude \"./**/*.d.ts\"", | ||
"test": "mocha-webpack --require source-map-support/register --webpack-config webpack.config.test.js \"test/**/*.tests.ts\"", | ||
"test-watch": "mocha-webpack --watch --require source-map-support/register --webpack-config webpack.config.test.js \"test/**/*.tests.ts\"", | ||
"release": "npm run lint && npm run test && webpack && webpack --config webpack.config.dev.js" | ||
}, | ||
"dependencies": { | ||
"lodash.get": "4.4.2", | ||
"lodash.isequal": "4.5.0", | ||
"lodash.snakecase": "4.1.1", | ||
"redux": "3.7.2", | ||
"reflect-metadata": "0.1.10" | ||
"redux": "3.7.2" | ||
}, | ||
@@ -56,3 +56,3 @@ "devDependencies": { | ||
"ts-nameof-loader": "1.0.1", | ||
"tslint": "5.8.0", | ||
"tslint": "^5.9.1", | ||
"typescript": "2.6.2", | ||
@@ -59,0 +59,0 @@ "webpack": "3.10.0", |
341
README.md
@@ -15,2 +15,8 @@ # redux-app | ||
```shell | ||
yarn add redux-app | ||
``` | ||
or | ||
```shell | ||
npm install --save redux-app | ||
@@ -22,3 +28,2 @@ ``` | ||
```javascript | ||
@component | ||
class App { | ||
@@ -28,6 +33,6 @@ counter = new Counter(); | ||
@component | ||
class Counter { | ||
value = 0; | ||
@action | ||
increment() { | ||
@@ -43,3 +48,3 @@ this.value = this.value + 1; // <--- see Important Notice below | ||
app.root.counter.increment(); // will dispatch COUNTER.INCREMENT redux action | ||
app.root.counter.increment(); // will dispatch a 'Counter.increment' redux action | ||
@@ -57,12 +62,12 @@ console.log(app.root.counter.value); // 1 | ||
More examples, including usage with [Angular](https://angular.io), [Aurelia](http://aurelia.io) and [React](https://reactjs.org/), can be found here [redux-app-examples](https://github.com/alonrbar/redux-app-examples). | ||
More examples, including usage with [Angular](https://angular.io) and [React](https://reactjs.org/), can be found here [redux-app-examples](https://github.com/alonrbar/redux-app-examples). | ||
## How it works | ||
For each `component` decorated class the library generates an underlying `Component` object that holds the same properties and methods. | ||
For each decorated class the library generates an underlying `Component` object that holds the same properties and methods. | ||
The new Component object has it's prototype patched and all of it's methods replaced with dispatch() calls. | ||
The generated Component also has a hidden 'reducer' property which is later on used by redux store. The 'reducer' property itself is | ||
generated from the original object methods, replacing all 'this' values with the current state from the store on each call (using | ||
Object.assign and Function.prototype.call). | ||
The generated Component also has a hidden 'reducer' property which is later on used by redux store. The 'reducer' property itself is generated from the original object methods, replacing all 'this' values with the current state from the store on each call (using Object.assign and Function.prototype.call). | ||
To make it easier to debug, each generated component name follows the following pattern: OriginalClassName_ReduxAppComponent. If while debugging you don't see the _ReduxAppComponent suffix it means the class was not replaced by an underlying component and is probably lacking a decorator (@action or @sequence). | ||
_Reading the source tip #1: There are two main classes in redux-app. The first is ReduxApp and the second is Component._ | ||
@@ -73,14 +78,14 @@ | ||
- [Stay Pure](#stay-pure) | ||
- [Features](#features) | ||
- Features | ||
- [Async Actions](#async-actions) | ||
- [The `withId` decorator - "mini ORM" feature](#withid) | ||
- [`connect` to the view](#withid) | ||
- [Computed Values](#computed-values) | ||
- [Utilities](#utilities) | ||
- [`ignoreState`](#ignorestate) | ||
- [`isInstanceOf`](#isinstanceof) | ||
- [Multiple Components of the Same Type](#multiple-components-of-the-same-type) | ||
- [Computed Values ("selectors")](#computed-values) | ||
- [Ignoring Parts of the State](#ignoring-parts-of-the-state) | ||
- [Connect to a view](#connect-to-a-view) | ||
- [React](#react) | ||
- [Angular and others](#angular-and-others) | ||
- Utilities | ||
- [isInstanceOf](#isinstanceof) | ||
- [Applying Enhancers (devtools, etc.)](#applying-enhancers) | ||
- [Options](#options) | ||
- [Component Options](#component-options) | ||
- [Computed Options](#computed-options) | ||
- Options | ||
- [App Options](#app-options) | ||
@@ -98,15 +103,10 @@ - [Global Options](#global-options) | ||
### Features | ||
### Async Actions | ||
#### Async Actions | ||
Async actions (thunks, sagas, epics...) and side effects are handled in redux-app by using the `sequence` decorator. | ||
What it does is to tell redux-app that the decorated method acts (almost) as a plain old javascript method. We say _almost_ since while the method body is executed regularly it still dispatches an action so it's still easy to track and log. | ||
Async actions (thunks, sagas, epics...) and side effects are handled in redux-app by using either the `sequence` decorator or the `noDispatch`. | ||
Both decorators does **exactly the same** and are actually aliases of the same underlying function. What they do is | ||
to tell redux-app that the decorated method is a plain old javascript method and that it should not be patched (about | ||
the patch process see [How it works](#how-it-works)). So, to conclude, what these decorators actually do is to tell | ||
redux-app to _do nothing special_ with the method. | ||
Remember: | ||
- Don't change the state inside `noDispatch` methods. | ||
- Don't change the state inside `sequence` methods. | ||
- If you need to dispatch a series of actions use the `sequence` decorator. Don't call actions from within other actions directly. | ||
@@ -119,9 +119,4 @@ | ||
```javascript | ||
@component | ||
class MyComponent { | ||
public setStatus(newStatus: string) { // <--- Not decorated. Will dispatch SET_STATUS action. | ||
this.status = newStatus; | ||
} | ||
@sequence | ||
@@ -145,8 +140,9 @@ public async fetchImage() { | ||
this.setStatus('I am done.'); | ||
}, 2000); | ||
} | ||
@noDispatch | ||
public doNothing() { | ||
console.log('I am a plain old method. Nothing special here.'); | ||
@action | ||
public setStatus(newStatus: string) { | ||
this.status = newStatus; | ||
} | ||
@@ -156,7 +152,5 @@ } | ||
#### withId | ||
### Multiple Components of the Same Type | ||
The role of the `withId` decorator is double. From one hand, it enables the co-existence of two (or more) instances of the same component, | ||
each with it's own separate state. From the other hand, it is used to keep two separate components in sync. Every component, when dispatching | ||
an action attaches it's ID to the action payload. The reducer in it's turn reacts only to actions targeting it's component ID. | ||
The role of the `withId` decorator is double. From one hand, it enables the co-existence of two (or more) instances of the same component, each with it's own separate state. From the other hand, it is used to keep two separate components in sync. Every component, when dispatching an action attaches it's ID to the action payload. The reducer in it's turn reacts only to actions targeting it's component ID. | ||
The 'id' argument of the decorator can be anything (string, number, object, etc.). | ||
@@ -169,16 +163,15 @@ | ||
```javascript | ||
@component | ||
export class App { | ||
@withId('SyncMe') | ||
public counter1 = new CounterComponent(); // <-- this counter is in sync with counter2 | ||
public counter1 = new Counter(); // <-- this counter is in sync with counter2 | ||
@withId('SyncMe') | ||
public counter2 = new CounterComponent(); // <-- this counter is in sync with counter1 | ||
public counter2 = new Counter(); // <-- this counter is in sync with counter1 | ||
@withId(123) | ||
public counter3 = new CounterComponent(); // <-- manual set ID | ||
public counter3 = new Counter(); // <-- manual set ID | ||
// this counter is not synced with the others | ||
@withId() | ||
public counter4 = new CounterComponent(); // <-- auto generated unique ID (unique within the scope of the application) | ||
public counter4 = new Counter(); // <-- auto generated unique ID (unique within the scope of the application) | ||
// this counter also has it's own unique state | ||
@@ -188,78 +181,65 @@ } | ||
#### connect | ||
### Connect to a view | ||
If you're using redux-app with Angular or Aurelia you may need an alternative to react-redux's connect. | ||
This is what this decorator is all about. | ||
You can leverage the following ReduxApp static method to connect your state components to your view: | ||
Connected components are *references* to other components. The connection is achieved using a "smart getter". | ||
It is smart in that sense that it waits for the target component to be available and than replace itself | ||
(i.e. the getter) with a simple reference to the target object, thus preventing further unnecessary invocations of the getter. | ||
```javascript | ||
ReduxApp.getComponent(componentType, componentId?, appId?) | ||
``` | ||
You can use IDs to connect to a specific component or omit the ID to connect to the first instance that redux-app finds. | ||
You can use IDs to retrieve a specific component or omit the ID to get the first instance that redux-app finds. | ||
You can connect a view to parts of the app tree, as shown in the next example. ~~You can also connect two, or more, components to a single source inside your app tree. To see a working example of the latter checkout the [examples](https://github.com/alonrbar/redux-app-examples) repository.~~ Connecting two components inside the app tree is deprecated. | ||
#### React | ||
**Remember**: When connecting components there should always be at least one non-connected instance of that component in your ReduxApp tree (a.k.a. the "source" component). | ||
_working example can be found on the [redux-app-examples](https://github.com/alonrbar/redux-app-examples) page_ | ||
Example: | ||
Use the snippet below to create an `autoSync` function. You can then use it as you would normally use react-redux's `connect`: | ||
_working example can be found on the [redux-app-examples](https://github.com/alonrbar/redux-app-examples) page_ | ||
```jsx | ||
const MyReactCounter: React.SFC<Counter> = (props) => ( | ||
<div> | ||
<span>Value: {props.value}</span> | ||
<button onClick={props.increment}>Increment</button> | ||
</div> | ||
); | ||
const synced = autoSync(Counter)(MyReactCounter); // <-- using 'autoSync' here | ||
export { synced as MyReactComponent }; | ||
``` | ||
The `autoSync` snippet: | ||
```javascript | ||
@component | ||
class App { | ||
public myComponent = new MyComponent(); | ||
} | ||
import { connect } from 'react-redux'; | ||
import { Constructor, getMethods, ReduxApp } from 'redux-app'; | ||
@component | ||
class MyComponent { | ||
public message = 'hello!'; | ||
export function autoSync<T>(stateType: Constructor<T>) { | ||
return connect<T>(() => { | ||
const comp = ReduxApp.getComponent(stateType); | ||
const compMethods = getMethods(comp, true); | ||
return Object.assign({}, comp, compMethods); | ||
}); | ||
} | ||
``` | ||
const app = new ReduxApp(new App()); | ||
#### Angular and others | ||
// and elsewhere, in a regular class: | ||
_working example can be found on the [redux-app-examples](https://github.com/alonrbar/redux-app-examples) page_ | ||
class MyView { | ||
@connect | ||
public myComponentReference: MyComponent; // <-- points to 'myComponent' of 'app'. | ||
} | ||
``` | ||
With Angular and similar frameworks (like Aurelia) it's as easy as: | ||
You can pass an optional 'options' argument to the `connect` decorator: | ||
```javascript | ||
class MyCounterView { | ||
public myCounterReference = ReduxApp.getComponent(Counter); | ||
```javascript | ||
export class ConnectOptions { | ||
/** | ||
* The name of the ReduxApp instance to connect to. | ||
* If not specified will connect to default app. | ||
*/ | ||
app?: string; | ||
/** | ||
* The ID of the target component (assuming the ID was assigned to the component | ||
* by the 'withId' decorator). | ||
* If not specified will connect to the first available component of that type. | ||
*/ | ||
id?: any; | ||
/** | ||
* The 'connect' decorator uses a getter to connect to the it's target. By | ||
* default the getter is replaced with a standard value (reference) once the | ||
* first non-empty value is retrieved. Set this value to true to leave the | ||
* getter in place. | ||
* Default value: false | ||
*/ | ||
live?: boolean; | ||
// other view logic here... | ||
} | ||
``` | ||
#### Computed Values | ||
### Computed Values | ||
It is possible to automatically calculate values from other parts of the components state (similar in concept to redux selectors). | ||
To do that just declare a getter and decorate it with the `computed` decorator. Behind the scenes redux-app will replace the getter | ||
with regular values and will take care of updating it after each change to the relevant state. | ||
To calculate values from other parts of the components state instead of using a fancy _selector_ function you can simply use a standard javascript getter. | ||
**Note:** As everything else, computed value getters should also be pure and should not mutate other parts of the state. | ||
**Remember:** As everything else, getters should be pure and should not mutate the state. | ||
**Note:** It is currently not possible to reliably compute values out of other `computed` values. | ||
Example: | ||
@@ -270,3 +250,2 @@ | ||
```javascript | ||
@component | ||
class ComputedGreeter { | ||
@@ -276,7 +255,7 @@ | ||
@computed | ||
public get welcomeString(): string { // <-- updates when 'name' changes | ||
return 'hello ' + this.name; | ||
public get welcomeString(): string { | ||
return 'Hello ' + this.name; | ||
} | ||
@action | ||
public setName(newVal: string) { | ||
@@ -288,6 +267,4 @@ this.name = newVal; | ||
### Utilities | ||
### Ignoring Parts of the State | ||
#### ignoreState | ||
You can use the `ignoreState` decorator to prevent particular properties of your components to be stored in the store. | ||
@@ -298,5 +275,4 @@ | ||
```javascript | ||
@component | ||
class MyComponent { | ||
public storeMe = 'hello'; | ||
@@ -306,2 +282,7 @@ | ||
public ignoreMe = 'not stored'; | ||
@action | ||
public changeState() { | ||
this.storeMe = 'I am stored'; | ||
} | ||
} | ||
@@ -315,3 +296,3 @@ | ||
#### isInstanceOf | ||
### isInstanceOf | ||
@@ -323,5 +304,7 @@ We've already said that classes decorated with the `component` decorator are being replaced at runtime | ||
```javascript | ||
@component | ||
class MyComponent { | ||
// ... | ||
@action | ||
public someAction() { | ||
// ... | ||
} | ||
} | ||
@@ -338,5 +321,7 @@ | ||
```javascript | ||
@component | ||
class MyComponent { | ||
// ... | ||
@action | ||
public someAction() { | ||
// ... | ||
} | ||
} | ||
@@ -370,62 +355,5 @@ | ||
### Options | ||
### App Options | ||
#### Component Options | ||
You can supply the following options to the `component` decorator. | ||
```javascript | ||
class SchemaOptions { | ||
/** | ||
* Add the class name of the object that holds the action to the action name. | ||
* Format: <class name><separator><action name> | ||
* Default value: true. | ||
*/ | ||
actionNamespace?: boolean; | ||
/** | ||
* Default value: . (dot) | ||
*/ | ||
actionNamespaceSeparator?: string; | ||
/** | ||
* Use redux style action names. For instance, if a component defines a | ||
* method called 'incrementCounter' the matching action name will be | ||
* 'INCREMENT_COUNTER'. | ||
* Default value: true. | ||
*/ | ||
uppercaseActions?: boolean; | ||
} | ||
``` | ||
Usage: | ||
```javascript | ||
@component({ uppercaseActions: false }) | ||
class Counter { | ||
value = 0; | ||
increment() { // <-- Will now dispatch 'Counter.increment' instead of 'COUNTER.INCREMENT'. Everything else still works the same, no further change required. | ||
this.value = this.value + 1; | ||
} | ||
} | ||
``` | ||
#### Computed Options | ||
Customize `computed` properties via `ReduxApp.options.computed`. | ||
```javascript | ||
class ComputedOptions { | ||
/** | ||
* Whether to perform deep comparison or a simple equality comparison | ||
* before updating computed values. Using deep comparison has a small | ||
* additional performance cost. | ||
* Default value: true. | ||
*/ | ||
deepComparison: boolean; | ||
} | ||
``` | ||
#### App Options | ||
```javascript | ||
export class AppOptions { | ||
@@ -453,6 +381,4 @@ /** | ||
#### Global Options | ||
### Global Options | ||
Available global options: | ||
```javascript | ||
@@ -465,49 +391,5 @@ class GlobalOptions { | ||
/** | ||
* When set to 'true' every component will have an additional __originalClassName__ property. | ||
* Can be useful for debugging. | ||
* Default value: false. | ||
* Customize actions naming. | ||
*/ | ||
emitClassNames: boolean; | ||
/** | ||
* From the original redux FAQ: | ||
* ---------------------------- | ||
* | ||
* Q: Can I put functions, promises, or other non-serializable items in my | ||
* store state? | ||
* | ||
* A: It is highly recommended that you only put plain serializable objects, | ||
* arrays, and primitives into your store. It's technically possible to | ||
* insert non-serializable items into the store, but doing so can break the | ||
* ability to persist and rehydrate the contents of a store, as well as | ||
* interfere with time-travel debugging. | ||
* | ||
* If you are okay with things like persistence and time-travel debugging | ||
* potentially not working as intended, then you are totally welcome to put | ||
* non-serializable items into your Redux store. Ultimately, it's your | ||
* application, and how you implement it is up to you. As with many other | ||
* things about Redux, just be sure you understand what tradeoffs are | ||
* involved. | ||
* | ||
* The case in redux-app: | ||
* ---------------------- | ||
* | ||
* By default redux-app aligns with redux recommendations and treats | ||
* everything stored in the store state as a plain object to prevent the | ||
* previously described issues. This approach may come with some performance | ||
* (and of course usability) cost. Therefor if you don't care about time | ||
* travel debugging or rehydration of the store content etc. and you don't | ||
* want to pay the aforementioned cost you can set this option to false. | ||
* | ||
* Default value: true. | ||
*/ | ||
convertToPlainObject?: boolean; | ||
/** | ||
* Global defaults. | ||
* Options supplied explicitly via the decorator will override options specified here. | ||
*/ | ||
schema: SchemaOptions; | ||
/** | ||
* Customize `computed` properties behavior. | ||
*/ | ||
computed: ComputedOptions; | ||
action: ActionOptions; | ||
} | ||
@@ -528,2 +410,22 @@ | ||
} | ||
class ActionOptions { | ||
/** | ||
* Add the class name of the object that holds the action to the action name. | ||
* Format: <class name><separator><action name> | ||
* Default value: true. | ||
*/ | ||
actionNamespace?: boolean; | ||
/** | ||
* Default value: . (dot) | ||
*/ | ||
actionNamespaceSeparator?: string; | ||
/** | ||
* Use redux style action names. For instance, if a component defines a | ||
* method called 'incrementCounter' the matching action name will be | ||
* 'INCREMENT_COUNTER'. | ||
* Default value: false. | ||
*/ | ||
uppercaseActions?: boolean; | ||
} | ||
``` | ||
@@ -535,2 +437,3 @@ | ||
ReduxApp.options.logLevel = LogLevel.Debug; | ||
ReduxApp.options.action.uppercaseActions = true; | ||
``` | ||
@@ -537,0 +440,0 @@ |
import { Action } from 'redux'; | ||
import { ComponentInfo, CreatorInfo, getCreatorMethods } from '../info'; | ||
import { globalOptions, SchemaOptions } from '../options'; | ||
import { ComponentInfo, ComponentTemplateInfo } from '../info'; | ||
import { ActionOptions, globalOptions } from '../options'; | ||
import { IMap, Method } from '../types'; | ||
import { getMethods } from '../utils'; | ||
import { Component } from './component'; | ||
@@ -16,8 +17,8 @@ var snakecase = require('lodash.snakecase'); | ||
public static createActions(creator: object): IMap<Method> { | ||
const methods = getCreatorMethods(creator); | ||
public static createActions(template: object): IMap<Method> { | ||
const methods = getMethods(template); | ||
if (!methods) | ||
return undefined; | ||
const creatorInfo = CreatorInfo.getInfo(creator); | ||
const templateInfo = ComponentTemplateInfo.getInfo(template); | ||
const componentActions: any = {}; | ||
@@ -33,7 +34,7 @@ Object.keys(methods).forEach(key => { | ||
// handle dispatch methods (use store dispatch) | ||
if (!creatorInfo.noDispatch[key]) { | ||
// handle actions and sequences (use store dispatch) | ||
if (templateInfo.actions[key] || templateInfo.sequences[key]) { | ||
const compInfo = ComponentInfo.getInfo(this); | ||
const action: ReduxAppAction = { | ||
type: ComponentActions.getActionName(creator, key, creatorInfo.options), | ||
type: ComponentActions.getActionName(template, key), | ||
id: (compInfo ? compInfo.id : undefined), | ||
@@ -45,4 +46,4 @@ payload: payload | ||
// handle non-dispatch methods (just call the function) | ||
if (creatorInfo.noDispatch[key] || creatorInfo.sequence[key]) { | ||
// handle regular methods (just call the function) | ||
if (!templateInfo.actions[key]) { | ||
return oldMethod.call(this, ...payload); | ||
@@ -56,7 +57,7 @@ } | ||
public static getActionName(creator: object, methodName: string, options?: SchemaOptions): string { | ||
options = Object.assign(new SchemaOptions(), globalOptions.schema, options); | ||
public static getActionName(template: object, methodName: string): string { | ||
const options = Object.assign(new ActionOptions(), globalOptions.action); | ||
var actionName = methodName; | ||
var actionNamespace = creator.constructor.name; | ||
var actionNamespace = template.constructor.name; | ||
@@ -63,0 +64,0 @@ if (options.uppercaseActions) { |
import { Store } from 'redux'; | ||
import { ComponentId, Connect } from '../decorators'; | ||
import { ClassInfo, ComponentInfo, CreatorInfo } from '../info'; | ||
import { globalOptions } from '../options'; | ||
import { ReduxApp, ROOT_COMPONENT_PATH } from '../reduxApp'; | ||
import { ComponentId } from '../decorators'; | ||
import { ClassInfo, ComponentInfo, ComponentTemplateInfo } from '../info'; | ||
import { ROOT_COMPONENT_PATH } from '../reduxApp'; | ||
import { IMap } from '../types'; | ||
import { isPrimitive, log } from '../utils'; | ||
import { defineProperties, DescriptorType, isPrimitive, log } from '../utils'; | ||
import { ComponentActions } from './actions'; | ||
@@ -12,7 +11,8 @@ import { ComponentReducer } from './reducer'; | ||
export class ComponentCreationContext { | ||
public visited = new Set(); | ||
public visitedNodes = new Set(); | ||
public visitedTemplates = new Map<object, Component>(); | ||
public path = ROOT_COMPONENT_PATH; | ||
public appName: string; | ||
public parentCreator: object; | ||
public parentTemplate: object; | ||
public createdComponents: IMap<Component> = {}; | ||
@@ -31,21 +31,18 @@ | ||
public static create(store: Store<any>, creator: object, context?: ComponentCreationContext): Component { | ||
public static create(store: Store<any>, template: object, context?: ComponentCreationContext): Component { | ||
context = Object.assign(new ComponentCreationContext(), context); | ||
// create the component | ||
var ComponentClass = Component.getComponentClass(creator); // tslint:disable-line:variable-name | ||
const component = new ComponentClass(store, creator, context as ComponentCreationContext); | ||
// create the component | ||
ComponentTemplateInfo.getOrInitInfo(template); | ||
const ComponentClass = Component.getComponentClass(template); // tslint:disable-line:variable-name | ||
const component = new ComponentClass(store, template, context as ComponentCreationContext); | ||
// register it on it's containing app | ||
ReduxApp.registerComponent(component, creator, context.appName); | ||
return component; | ||
} | ||
private static getComponentClass(creator: object): typeof Component { | ||
var info = CreatorInfo.getInfo(creator); | ||
private static getComponentClass(template: object): typeof Component { | ||
const info = ComponentTemplateInfo.getInfo(template); | ||
if (!info.componentClass) { | ||
info.componentClass = Component.createComponentClass(creator); | ||
info.originalClass = creator.constructor; | ||
info.componentClass = Component.createComponentClass(template); | ||
} | ||
@@ -55,18 +52,15 @@ return info.componentClass; | ||
private static createComponentClass(creator: object) { | ||
private static createComponentClass(template: object): typeof Component { | ||
// declare new class | ||
class ComponentClass extends Component { | ||
public __originalClassName__ = creator.constructor.name; // tslint:disable-line:variable-name | ||
// create new component class | ||
const componentClassFactory = new Function( | ||
'initCallback', | ||
`"use strict";return function ${template.constructor.name}_ReduxAppComponent() { initCallback(this, arguments); }` | ||
); | ||
const ComponentClass = componentClassFactory((self: any, args: any) => Component.apply(self, args)); // tslint:disable-line:variable-name | ||
ComponentClass.prototype = Object.create(Component.prototype); | ||
ComponentClass.prototype.constructor = ComponentClass; | ||
constructor(store: Store<any>, creatorArg: object, context: ComponentCreationContext) { | ||
super(store, creatorArg, context); | ||
if (!globalOptions.emitClassNames) | ||
delete this.__originalClassName__; | ||
} | ||
} | ||
// patch it's prototype | ||
const actions = ComponentActions.createActions(creator); | ||
const actions = ComponentActions.createActions(template); | ||
Object.assign(ComponentClass.prototype, actions); | ||
@@ -77,69 +71,60 @@ | ||
private static createSelf(component: Component, store: Store<object>, creator: object, context: ComponentCreationContext): void { | ||
private static createSelf(component: Component, store: Store<object>, template: object, context: ComponentCreationContext): void { | ||
// regular js props (including getters and setters) | ||
for (let key of Object.getOwnPropertyNames(creator)) { | ||
var desc = Object.getOwnPropertyDescriptor(creator, key); | ||
Object.defineProperty(component, key, desc); | ||
} | ||
defineProperties(component, template, [DescriptorType.Field, DescriptorType.Property]); | ||
// init component info | ||
const selfInfo = ComponentInfo.initInfo(component); | ||
const id = ComponentId.getComponentId(context.parentTemplate, context.path); | ||
const selfInfo = ComponentInfo.initInfo(component, template, store.dispatch, id); | ||
// copy info from template | ||
const selfClassInfo = ClassInfo.getOrInitInfo(component); | ||
const templateClassInfo = ClassInfo.getInfo(template) || new ClassInfo(); | ||
selfClassInfo.ignoreState = templateClassInfo.ignoreState; | ||
// copy info from creator | ||
const creatorInfo = CreatorInfo.getInfo(creator); | ||
const creatorClassInfo = ClassInfo.getInfo(creator) || new ClassInfo(); | ||
selfInfo.id = ComponentId.getComponentId(context.parentCreator, context.path); | ||
selfInfo.originalClass = creatorInfo.originalClass; | ||
selfClassInfo.computedGetters = creatorClassInfo.computedGetters; | ||
selfClassInfo.ignoreState = creatorClassInfo.ignoreState; | ||
// connected props | ||
Connect.setupConnectedProps(component, selfClassInfo, creator, creatorClassInfo); | ||
// dispatch | ||
selfInfo.dispatch = store.dispatch; | ||
// reducer | ||
selfInfo.reducerCreator = ComponentReducer.createReducer(component, creator); | ||
selfInfo.reducerCreator = ComponentReducer.createReducer(component, template); | ||
} | ||
private static createSubComponents(obj: any, store: Store<object>, creator: object, context: ComponentCreationContext): void { | ||
private static createSubComponents(treeNode: any, store: Store<object>, template: object, context: ComponentCreationContext): void { | ||
// no need to search inside primitives | ||
if (isPrimitive(obj)) | ||
if (isPrimitive(treeNode)) | ||
return; | ||
// prevent endless loops on circular references | ||
if (context.visited.has(obj)) | ||
if (context.visitedNodes.has(treeNode)) | ||
return; | ||
context.visited.add(obj); | ||
context.visitedNodes.add(treeNode); | ||
// traverse object children | ||
const searchIn = creator || obj; | ||
// traverse children | ||
const searchIn = template || treeNode; | ||
for (let key of Object.keys(searchIn)) { | ||
const connectionInfo = Connect.isConnectedProperty(obj, key); | ||
if (connectionInfo) | ||
continue; | ||
var subPath = context.path + '.' + key; | ||
var subCreator = searchIn[key]; | ||
if (CreatorInfo.getInfo(subCreator)) { | ||
var subTemplate = searchIn[key]; | ||
if (ComponentTemplateInfo.getInfo(subTemplate)) { | ||
// child is sub-component | ||
obj[key] = Component.create(store, subCreator, { | ||
...context, | ||
parentCreator: creator, | ||
path: subPath | ||
}); | ||
if (context.visitedTemplates.has(subTemplate)) { | ||
// child is an existing sub-component | ||
treeNode[key] = context.visitedTemplates.get(subTemplate); | ||
} else { | ||
// child is a new sub-component | ||
treeNode[key] = Component.create(store, subTemplate, { | ||
...context, | ||
parentTemplate: template, | ||
path: subPath | ||
}); | ||
} | ||
} else { | ||
// child is regular object, nothing special to do with it | ||
Component.createSubComponents(obj[key], store, null, { | ||
Component.createSubComponents(treeNode[key], store, null, { | ||
...context, | ||
parentCreator: null, | ||
parentTemplate: null, | ||
path: subPath | ||
@@ -155,13 +140,13 @@ }); | ||
private constructor(store: Store<any>, creator: object, context: ComponentCreationContext) { | ||
private constructor(store: Store<any>, template: object, context: ComponentCreationContext) { | ||
if (!CreatorInfo.getInfo(creator)) | ||
throw new Error(`Argument '${nameof(creator)}' is not a component creator. Did you forget to use the decorator?`); | ||
if (!ComponentTemplateInfo.getInfo(template)) | ||
throw new Error(`Argument '${nameof(template)}' is not a component template. Did you forget to use the decorator?`); | ||
Component.createSelf(this, store, creator, context); | ||
Component.createSubComponents(this, store, creator, context); | ||
Component.createSelf(this, store, template, context); | ||
context.createdComponents[context.path] = this; | ||
context.visitedTemplates.set(template, this); | ||
log.verbose(`[Component] New ${template.constructor.name} component created. Path: ${context.path}`); | ||
context.createdComponents[context.path] = this; | ||
log.verbose(`[Component] New ${creator.constructor.name} component created. Path: ${context.path}`); | ||
Component.createSubComponents(this, store, template, context); | ||
} | ||
@@ -168,0 +153,0 @@ |
import { Reducer, ReducersMapObject } from 'redux'; | ||
import { Computed, Connect, IgnoreState } from '../decorators'; | ||
import { ComponentInfo, CreatorInfo, getCreatorMethods } from '../info'; | ||
import { IgnoreState } from '../decorators'; | ||
import { ComponentInfo, ComponentTemplateInfo } from '../info'; | ||
import { ROOT_COMPONENT_PATH } from '../reduxApp'; | ||
import { IMap, Listener, Method } from '../types'; | ||
import { clearProperties, getMethods, isPrimitive, log, simpleCombineReducers } from '../utils'; | ||
import { IMap, Listener } from '../types'; | ||
import { clearProperties, defineProperties, DescriptorType, getMethods, isPrimitive, log, simpleCombineReducers } from '../utils'; | ||
import { ComponentActions, ReduxAppAction } from './actions'; | ||
@@ -40,28 +40,35 @@ import { Component } from './component'; | ||
public static createReducer(component: Component, componentCreator: object): ReducerCreator { | ||
public static createReducer(component: Component, componentTemplate: object): ReducerCreator { | ||
const creatorInfo = CreatorInfo.getInfo(componentCreator); | ||
if (!creatorInfo) | ||
throw new Error(`Inconsistent component '${componentCreator.constructor.name}'. The 'component' class decorator is missing.`); | ||
const templateInfo = ComponentTemplateInfo.getInfo(componentTemplate); | ||
if (!templateInfo) | ||
throw new Error(`Inconsistent component '${componentTemplate.constructor.name}'. The 'component' class decorator is missing.`); | ||
const methods = ComponentReducer.createMethodsLookup(componentCreator, creatorInfo); | ||
const stateProto = ComponentReducer.createStateObjectPrototype(component, creatorInfo); | ||
const methods = ComponentReducer.createMethodsLookup(componentTemplate, templateInfo); | ||
const stateProto = ComponentReducer.createStateObjectPrototype(component, templateInfo); | ||
const componentId = ComponentInfo.getInfo(component).id; | ||
return (changeListener: Listener<Component>) => { | ||
// reducer creator | ||
return (changeListener: Listener<Component>) => | ||
// the reducer | ||
return (state: object, action: ReduxAppAction) => { | ||
(state: object, action: ReduxAppAction) => { | ||
log.verbose(`[reducer] Reducer of: ${componentCreator.constructor.name}, action: ${action.type}`); | ||
log.verbose(`[reducer] Reducer of: ${componentTemplate.constructor.name}, action: ${action.type}.`); | ||
// initial state | ||
if (state === undefined) { | ||
log.verbose('[reducer] State is undefined, returning initial value'); | ||
return component; | ||
log.verbose('[reducer] State is undefined, returning initial value.'); | ||
return ComponentReducer.finalizeStateObject(component, component); | ||
} | ||
// preloaded state | ||
if (state === componentTemplate) { | ||
log.verbose("[reducer] State equals to component's template, returning initial value."); | ||
return ComponentReducer.finalizeStateObject(component, component); | ||
} | ||
// check component id | ||
if (componentId !== action.id) { | ||
log.verbose(`[reducer] Component id and action.id don't match (${componentId} !== ${action.id})`); | ||
log.verbose(`[reducer] Component id and action.id don't match (${componentId} !== ${action.id}).`); | ||
return state; | ||
@@ -73,3 +80,3 @@ } | ||
if (!actionReducer) { | ||
log.verbose('[reducer] No matching action in this reducer, returning previous state'); | ||
log.verbose('[reducer] No matching action in this reducer, returning previous state.'); | ||
return state; | ||
@@ -86,6 +93,5 @@ } | ||
// return new state | ||
log.verbose('[reducer] Reducer invoked, returning new state'); | ||
return newState; | ||
log.verbose('[reducer] Reducer invoked, returning new state.'); | ||
return ComponentReducer.finalizeStateObject(newState, component); | ||
}; | ||
}; | ||
} | ||
@@ -103,4 +109,3 @@ | ||
let newState = reducer(state, action); | ||
newState = ComponentReducer.finalizeState(newState, root); | ||
const newState = reducer(state, action); | ||
@@ -117,17 +122,11 @@ const end = Date.now(); | ||
// | ||
private static createMethodsLookup(componentCreator: object, creatorInfo: CreatorInfo): IMap<Function> { | ||
const allMethods = getCreatorMethods(componentCreator); | ||
const options = creatorInfo.options; | ||
const actionMethods: IMap<Function> = {}; | ||
Object.keys(allMethods).forEach(methName => { | ||
private static createMethodsLookup(componentTemplate: object, templateInfo: ComponentTemplateInfo): IMap<Function> { | ||
// reducers does not handle 'noDispatch' and 'sequence' methods | ||
if (creatorInfo.noDispatch[methName] || creatorInfo.sequence[methName]) | ||
return; | ||
const allMethods = getMethods(componentTemplate); | ||
var actionName = ComponentActions.getActionName(componentCreator, methName, options); | ||
actionMethods[actionName] = allMethods[methName]; | ||
const actionMethods: IMap<Function> = {}; | ||
Object.keys(templateInfo.actions).forEach(originalActionName => { | ||
const normalizedActionName = ComponentActions.getActionName(componentTemplate, originalActionName); | ||
actionMethods[normalizedActionName] = allMethods[originalActionName]; | ||
}); | ||
@@ -141,9 +140,15 @@ | ||
*/ | ||
private static createStateObjectPrototype(component: Component, creatorInfo: CreatorInfo): object { | ||
const stateProto: IMap<Method> = {}; | ||
private static createStateObjectPrototype(component: Component, templateInfo: ComponentTemplateInfo): object { | ||
// assign properties | ||
const stateProto: any = defineProperties({}, component, [DescriptorType.Property]); | ||
// assign methods | ||
const componentMethods = getMethods(component); | ||
for (let key of Object.keys(componentMethods)) { | ||
if (creatorInfo.noDispatch[key]) { | ||
if (!templateInfo.actions[key]) { | ||
// regular method | ||
stateProto[key] = componentMethods[key].bind(component); | ||
} else { | ||
// action (not allowed) | ||
stateProto[key] = ComponentReducer.actionInvokedError; | ||
@@ -156,3 +161,3 @@ } | ||
private static actionInvokedError() { | ||
throw new Error("Only 'noDispatch' methods can be invoked inside actions."); | ||
throw new Error("Actions should not be invoked from within other actions."); | ||
} | ||
@@ -163,11 +168,30 @@ | ||
* the current state and it's methods from the owning component. Methods | ||
* that represent actions are replace with a throw call, while noDispatch | ||
* that represent actions are replace with a throw call, while regular | ||
* methods are kept in place. | ||
*/ | ||
private static createStateObject(state: object, stateProto: object): object { | ||
private static createStateObject(state: any, stateProto: object): object { | ||
const stateObj = Object.create(stateProto); | ||
Object.assign(stateObj, state); | ||
for (const key of Object.keys(state)) { | ||
// don't attempt to assign get only properties | ||
const desc = Object.getOwnPropertyDescriptor(stateProto, key); | ||
if (desc && typeof desc.get === 'function' && typeof desc.set !== 'function') | ||
continue; | ||
stateObj[key] = state[key]; | ||
} | ||
return stateObj; | ||
} | ||
private static finalizeStateObject(state: object, component: Component): object { | ||
log.verbose('[finalizeStateObject] finalizing state.'); | ||
let finalizedState = Object.assign({}, state); | ||
finalizedState = IgnoreState.removeIgnoredProps(finalizedState, component); | ||
log.verbose('[finalizeStateObject] state finalized.'); | ||
return finalizedState; | ||
} | ||
// | ||
@@ -207,6 +231,2 @@ // private methods - combine reducers | ||
// connected components are modified only by their source | ||
if (Connect.isConnectedProperty(obj, key)) | ||
continue; | ||
// other objects | ||
@@ -260,42 +280,2 @@ const newSubReducer = ComponentReducer.combineReducersRecursion((obj as any)[key], new CombineReducersContext({ | ||
} | ||
private static finalizeState(rootState: any, root: Component): any { | ||
return ComponentReducer.finalizeStateRecursion(rootState, root, new Set()); | ||
} | ||
private static finalizeStateRecursion(state: any, obj: any, visited: Set<any>): any { | ||
// primitive properties are updated by their owner objects | ||
if (isPrimitive(state) || isPrimitive(obj)) | ||
return state; | ||
// prevent endless loops on circular references | ||
if (visited.has(state)) | ||
return state; | ||
visited.add(state); | ||
const handledProps = {}; | ||
state = Connect.removeConnectedProps(state, obj, handledProps); | ||
state = Computed.removeComputedProps(state, obj, handledProps); | ||
state = IgnoreState.removeIgnoredProps(state, obj, handledProps); | ||
// transform children | ||
Object.keys(state).forEach(key => { | ||
// skip already handled properties | ||
if (handledProps.hasOwnProperty(key)) | ||
return; | ||
var subState = state[key]; | ||
var subObj = obj[key]; | ||
var newSubState = ComponentReducer.finalizeStateRecursion(subState, subObj, visited); | ||
// assign only if changed | ||
if (newSubState !== subState) { | ||
state[key] = newSubState; | ||
} | ||
}); | ||
return state; | ||
} | ||
} |
import { ClassInfo } from '../info'; | ||
import { IMap } from '../types'; | ||
@@ -21,3 +20,3 @@ /** | ||
public static removeIgnoredProps(state: any, obj: any, ignoredProps: IMap<any>): any { | ||
public static removeIgnoredProps(state: any, obj: any): any { | ||
@@ -28,12 +27,8 @@ const info = ClassInfo.getInfo(obj); | ||
// populate output parameter | ||
Object.assign(ignoredProps, info.ignoreState); | ||
// remove ignored props | ||
const newState = Object.assign({}, state); | ||
for (let propKey of Object.keys(info.ignoreState)) { | ||
delete newState[propKey]; | ||
delete state[propKey]; | ||
} | ||
return newState; | ||
return state; | ||
} | ||
} |
@@ -1,7 +0,4 @@ | ||
export * from './connect'; | ||
export * from './component'; | ||
export * from './computed'; | ||
export * from './action'; | ||
export * from './ignoreState'; | ||
export * from './noDispatch'; | ||
export * from './sequence'; | ||
export * from './withId'; |
@@ -1,2 +0,2 @@ | ||
import { CreatorInfo } from '../info'; | ||
import { ComponentTemplateInfo } from '../info'; | ||
@@ -12,4 +12,4 @@ /** | ||
export function sequence(target: object, propertyKey: string | symbol): void { | ||
const info = CreatorInfo.getOrInitInfo(target); | ||
info.sequence[propertyKey] = true; | ||
const info = ComponentTemplateInfo.getOrInitInfo(target); | ||
info.sequences[propertyKey] = true; | ||
} |
@@ -1,2 +0,2 @@ | ||
import { CreatorInfo } from '../info'; | ||
import { ComponentTemplateInfo } from '../info'; | ||
import { AUTO_ID } from '../symbols'; | ||
@@ -16,3 +16,3 @@ import { log } from '../utils'; | ||
function withIdDecorator(target: object, propertyKey: string | symbol, id?: any) { | ||
const info = CreatorInfo.getOrInitInfo(target); | ||
const info = ComponentTemplateInfo.getOrInitInfo(target); | ||
info.childIds[propertyKey] = id || AUTO_ID; | ||
@@ -29,11 +29,11 @@ } | ||
public static getComponentId(parentCreator: object, path: string): any { | ||
public static getComponentId(parentTemplate: object, path: string): any { | ||
// | ||
// Note: The component id is first stored on it's parent. It can be only | ||
// Note: The component id is first stored on it's parent. It is only | ||
// assigned to it once the component itself has been constructed. The | ||
// differed assigned resolves situations where the component is created | ||
// inside it's parent constructor or injected via DI. This could have | ||
// been solved with custom getter and setter but decided to go with this | ||
// approach here. | ||
// differed assignment resolves situations where the component`s | ||
// template is created inside it's parent constructor or injected via | ||
// DI. This could have also been resolved with custom getter and setter | ||
// but decided to take this approach here. | ||
// | ||
@@ -43,7 +43,7 @@ | ||
const pathArray = path.split('.'); | ||
if (!parentCreator || !pathArray.length) | ||
if (!parentTemplate || !pathArray.length) | ||
return undefined; | ||
// parent is not a component creator | ||
const info = CreatorInfo.getInfo(parentCreator); | ||
// parent is not a component template | ||
const info = ComponentTemplateInfo.getInfo(parentTemplate); | ||
if (!info) | ||
@@ -50,0 +50,0 @@ return; |
export { isInstanceOf } from './components'; | ||
export { component, connect, computed, ignoreState, noDispatch, sequence, withId } from './decorators'; | ||
export { SchemaOptions, ComputedOptions, AppOptions, GlobalOptions, LogLevel } from './options'; | ||
export { ReduxApp } from './reduxApp'; | ||
export { getMethods } from './utils'; | ||
export { action, ignoreState, sequence, withId } from './decorators'; | ||
export { ActionOptions, AppOptions, GlobalOptions, LogLevel } from './options'; | ||
export { getMethods } from './utils'; | ||
export { ReduxApp } from './reduxApp'; |
import { CLASS_INFO, getSymbol, setSymbol } from '../symbols'; | ||
import { Getter, IMap } from '../types'; | ||
import { IMap } from '../types'; | ||
@@ -29,5 +29,3 @@ /** | ||
public computedGetters: IMap<Getter> = {}; | ||
public connectedProps: IMap<boolean> = {}; | ||
public ignoreState: IMap<boolean> = {}; | ||
} |
@@ -19,10 +19,17 @@ import { Dispatch } from 'redux'; | ||
public static initInfo(component: Component): ComponentInfo { | ||
return setSymbol(component, COMPONENT_INFO, new ComponentInfo()); | ||
public static initInfo(component: Component, template: object, dispatch: Dispatch<any>, id: any): ComponentInfo { | ||
const info = new ComponentInfo(template, dispatch, id); | ||
return setSymbol(component, COMPONENT_INFO, info); | ||
} | ||
public id: any; | ||
public originalClass: Function; | ||
public dispatch: Dispatch<any>; | ||
public readonly id: any; | ||
public readonly originalClass: Function; | ||
public readonly dispatch: Dispatch<any>; | ||
public reducerCreator: ReducerCreator; | ||
constructor(template: object, dispatch: Dispatch<any>, id: any) { | ||
this.originalClass = template.constructor; | ||
this.dispatch = dispatch; | ||
this.id = id; | ||
} | ||
} |
export * from './classInfo'; | ||
export * from './componentInfo'; | ||
export * from './creatorInfo'; | ||
export * from './creatorMethods'; | ||
export * from './componentTemplateInfo'; |
// tslint:disable:whitespace | ||
// | ||
// schema options | ||
// action options | ||
// | ||
export class SchemaOptions { | ||
export class ActionOptions { | ||
/** | ||
@@ -23,20 +25,6 @@ * Add the class name of the object that holds the action to the action name. | ||
*/ | ||
public uppercaseActions? = true; | ||
public uppercaseActions? = false; | ||
} | ||
// | ||
// computed options | ||
// | ||
export class ComputedOptions { | ||
/** | ||
* Whether to perform deep comparison or a simple equality comparison | ||
* before updating computed values. Using deep comparison has a small | ||
* additional performance cost. | ||
* Default value: true. | ||
*/ | ||
public deepComparison = true; | ||
} | ||
// | ||
// app options | ||
@@ -84,49 +72,7 @@ // | ||
/** | ||
* When set to 'true' every component will have an additional __originalClassName__ property. | ||
* Can be useful for debugging. | ||
* Default value: false. | ||
* Customize actions naming. | ||
*/ | ||
public emitClassNames = false; | ||
/** | ||
* #### From the original redux FAQ: | ||
* | ||
* Q: Can I put functions, promises, or other non-serializable items in my | ||
* store state? | ||
* | ||
* A: It is highly recommended that you only put plain serializable objects, | ||
* arrays, and primitives into your store. It's technically possible to | ||
* insert non-serializable items into the store, but doing so can break the | ||
* ability to persist and rehydrate the contents of a store, as well as | ||
* interfere with time-travel debugging. | ||
* | ||
* If you are okay with things like persistence and time-travel debugging | ||
* potentially not working as intended, then you are totally welcome to put | ||
* non-serializable items into your Redux store. Ultimately, it's your | ||
* application, and how you implement it is up to you. As with many other | ||
* things about Redux, just be sure you understand what tradeoffs are | ||
* involved. | ||
* | ||
* #### The case in redux-app: | ||
* | ||
* By default redux-app aligns with redux recommendations and treats | ||
* everything stored in the store state as a plain object to prevent the | ||
* previously described issues. This approach may come with some performance | ||
* (and of course usability) cost. Therefor if you don't care about time | ||
* travel debugging or rehydration of the store content etc. and you don't | ||
* want to pay the aforementioned cost you can set this option to false. | ||
* | ||
* Default value: true. | ||
*/ | ||
public convertToPlainObject? = true; | ||
/** | ||
* Global defaults. | ||
* Options supplied explicitly via the decorator will override options specified here. | ||
*/ | ||
public schema = new SchemaOptions(); | ||
/** | ||
* Customize `computed` properties behavior. | ||
*/ | ||
public computed = new ComputedOptions(); | ||
public action = new ActionOptions(); | ||
} | ||
export const globalOptions = new GlobalOptions(); |
import { createStore, Store, StoreEnhancer } from 'redux'; | ||
import { CombineReducersContext, Component, ComponentCreationContext, ComponentReducer } from './components'; | ||
import { ComponentId, Computed, Connect, IgnoreState } from './decorators'; | ||
import { ComponentId, IgnoreState } from './decorators'; | ||
import { ComponentInfo } from './info'; | ||
import { AppOptions, globalOptions, GlobalOptions } from './options'; | ||
import { Constructor, IMap, Listener } from './types'; | ||
import { isPrimitive, log, toPlainObject } from './utils'; | ||
import { isPrimitive, log } from './utils'; | ||
const getProp = require('lodash.get'); | ||
@@ -52,24 +52,10 @@ | ||
public static createApp<T extends object>(appCreator: T, enhancer?: StoreEnhancer<T>): ReduxApp<T>; | ||
public static createApp<T extends object>(appCreator: T, options: AppOptions, enhancer?: StoreEnhancer<T>): ReduxApp<T>; | ||
public static createApp<T extends object>(appCreator: T, options: AppOptions, preloadedState: any, enhancer?: StoreEnhancer<T>): ReduxApp<T>; | ||
public static createApp<T extends object>(appCreator: T, ...params: any[]): ReduxApp<T> { | ||
return new ReduxApp(appCreator, ...params); | ||
public static createApp<T extends object>(appTemplate: T, enhancer?: StoreEnhancer<T>): ReduxApp<T>; | ||
public static createApp<T extends object>(appTemplate: T, options: AppOptions, enhancer?: StoreEnhancer<T>): ReduxApp<T>; | ||
public static createApp<T extends object>(appTemplate: T, options: AppOptions, preloadedState: any, enhancer?: StoreEnhancer<T>): ReduxApp<T>; | ||
public static createApp<T extends object>(appTemplate: T, ...params: any[]): ReduxApp<T> { | ||
return new ReduxApp(appTemplate, ...params); | ||
} | ||
/** | ||
* Get an existing ReduxApp instance. | ||
* | ||
* @param appId The name of the ReduxApp instance to retrieve. If not | ||
* specified will return the default app. | ||
*/ | ||
public static getApp<T extends object = any>(appId?: string): ReduxApp<T> { | ||
const applicationId = appId || DEFAULT_APP_NAME; | ||
const app = appsRepository[applicationId]; | ||
if (!app) | ||
log.debug(`[ReduxApp] Application '${applicationId}' does not exist.`); | ||
return app; | ||
} | ||
/** | ||
* @param type The type of the component. | ||
@@ -81,9 +67,10 @@ * @param componentId The ID of the component (assuming the ID was assigned | ||
* specified will search in default app. | ||
* @throws If not found. | ||
*/ | ||
public static getComponent<T>(type: Constructor<T>, componentId?: string, appId?: string): T { | ||
const app = ReduxApp.getApp(appId); | ||
if (!app) | ||
return undefined; | ||
if (!app) | ||
throw new Error(`App not found (id: '${appId || DEFAULT_APP_NAME}')`); | ||
// get the component to connect | ||
// get the component | ||
const warehouse = app.getTypeWarehouse(type); | ||
@@ -93,7 +80,13 @@ if (componentId) { | ||
// get by id | ||
return warehouse.get(componentId); | ||
const comp = warehouse.get(componentId); | ||
if (!comp) | ||
throw new Error(`Component not found. Type: ${type.name}. Id: '${componentId}'.`); | ||
return comp; | ||
} else { | ||
// get the first value | ||
return warehouse.values().next().value; | ||
const comp = warehouse.values().next().value; | ||
if (!comp) | ||
throw new Error(`Component not found. Type: ${type.name}.`); | ||
return comp; | ||
} | ||
@@ -103,12 +96,13 @@ } | ||
/** | ||
* INTERNAL: Should not appear on the public d.ts file. | ||
* Get an existing ReduxApp instance. | ||
* | ||
* @param appId The name of the ReduxApp instance to retrieve. If not | ||
* specified will return the default app. | ||
*/ | ||
public static registerComponent(comp: Component, creator: object, appName?: string): void { | ||
appName = appName || DEFAULT_APP_NAME; | ||
const app = appsRepository[appName]; | ||
if (app) { // this check exists for test reason only - in some unit tests we create orphan components that are not part of any app... | ||
const warehouse = app.getTypeWarehouse(creator.constructor); | ||
const key = ComponentInfo.getInfo(comp).id || ComponentId.nextAvailableId(); | ||
warehouse.set(key, comp); | ||
} | ||
private static getApp<T extends object = any>(appId?: string): ReduxApp<T> { | ||
const applicationId = appId || DEFAULT_APP_NAME; | ||
const app = appsRepository[applicationId]; | ||
if (!app) | ||
log.debug(`[ReduxApp] Application '${applicationId}' does not exist.`); | ||
return app; | ||
} | ||
@@ -140,9 +134,9 @@ | ||
constructor(appCreator: T, enhancer?: StoreEnhancer<T>); | ||
constructor(appCreator: T, options: AppOptions, enhancer?: StoreEnhancer<T>); | ||
constructor(appCreator: T, options: AppOptions, preloadedState: any, enhancer?: StoreEnhancer<T>); | ||
constructor(appCreator: T, ...params: any[]) { | ||
constructor(appTemplate: T, enhancer?: StoreEnhancer<T>); | ||
constructor(appTemplate: T, options: AppOptions, enhancer?: StoreEnhancer<T>); | ||
constructor(appTemplate: T, options: AppOptions, preloadedState: any, enhancer?: StoreEnhancer<T>); | ||
constructor(appTemplate: T, ...params: any[]) { | ||
// handle different overloads | ||
var { options, preLoadedState, enhancer } = this.resolveParameters(appCreator, params); | ||
var { options, preLoadedState, enhancer } = this.resolveParameters(appTemplate, params); | ||
@@ -155,3 +149,3 @@ // assign name and register self | ||
// create the store | ||
// create the store | ||
const initialReducer = (state: any) => state; | ||
@@ -162,4 +156,5 @@ this.store = createStore<T>(initialReducer as any, preLoadedState, enhancer); | ||
const creationContext = new ComponentCreationContext({ appName: this.name }); | ||
const rootComponent = Component.create(this.store, appCreator, creationContext); | ||
const rootComponent = Component.create(this.store, appTemplate, creationContext); | ||
this.root = (rootComponent as any); | ||
this.registerComponents(creationContext.createdComponents); | ||
@@ -172,7 +167,9 @@ // create the root reducer | ||
// update the store | ||
// listen to state changes | ||
if (options.updateState) { | ||
const stateListener = this.updateState(creationContext.createdComponents, reducersContext); | ||
const stateListener = this.updateState(reducersContext); | ||
this.subscriptionDisposer = this.store.subscribe(stateListener); | ||
} | ||
// update the store | ||
this.store.replaceReducer(rootReducer); | ||
@@ -195,16 +192,7 @@ } | ||
/** | ||
* INTERNAL: Should not appear on the public d.ts file. | ||
*/ | ||
public getTypeWarehouse(type: Function): Map<any, any> { | ||
if (!this.warehouse.has(type)) | ||
this.warehouse.set(type, new Map()); | ||
return this.warehouse.get(type); | ||
} | ||
// | ||
// private utils | ||
// | ||
// | ||
private resolveParameters(appCreator: any, params: any[]) { | ||
private resolveParameters(appTemplate: any, params: any[]) { | ||
var result: { | ||
@@ -220,3 +208,3 @@ options?: AppOptions, | ||
result.options = new AppOptions(); | ||
result.preLoadedState = appCreator; | ||
result.preLoadedState = appTemplate; | ||
@@ -230,3 +218,3 @@ } else if (params.length === 1) { | ||
result.enhancer = params[0]; | ||
result.preLoadedState = appCreator; | ||
result.preLoadedState = appTemplate; | ||
@@ -237,3 +225,3 @@ } else { | ||
result.options = Object.assign(new AppOptions(), params[0]); | ||
result.preLoadedState = appCreator; | ||
result.preLoadedState = appTemplate; | ||
@@ -269,2 +257,17 @@ } | ||
private registerComponents(components: IMap<Component>): void { | ||
for (const comp of Object.values(components)) { | ||
const compInfo = ComponentInfo.getInfo(comp); | ||
const warehouse = this.getTypeWarehouse(compInfo.originalClass); | ||
const key = compInfo.id || ComponentId.nextAvailableId(); | ||
warehouse.set(key, comp); | ||
} | ||
} | ||
private getTypeWarehouse(type: Function): Map<any, any> { | ||
if (!this.warehouse.has(type)) | ||
this.warehouse.set(type, new Map()); | ||
return this.warehouse.get(type); | ||
} | ||
// | ||
@@ -274,6 +277,3 @@ // update state | ||
private updateState(allComponents: IMap<Component>, reducersContext: CombineReducersContext): Listener { | ||
const withComputedProps = Computed.filterComponents(Object.values(allComponents)); | ||
private updateState(reducersContext: CombineReducersContext): Listener { | ||
return () => { | ||
@@ -301,6 +301,2 @@ | ||
// because computed props may be dependant on connected props their | ||
// calculation is postponed to after the entire app tree is up-to-date | ||
Computed.computeProps(withComputedProps); | ||
// reset reducers context | ||
@@ -347,12 +343,5 @@ reducersContext.reset(); | ||
// update | ||
const newStateType = newState.constructor; | ||
if (context.forceRecursion || (obj instanceof Component)) { | ||
// convert to plain object (see comment on the option itself) | ||
if (globalOptions.convertToPlainObject) | ||
newState = toPlainObject(newState); | ||
if (context.forceRecursion || (obj instanceof Component && newStateType === Object)) { | ||
// update if new state is a plain object (so to keep methods while updating props) | ||
// update | ||
var changeMessage: string; | ||
@@ -384,30 +373,36 @@ if (Array.isArray(obj) && Array.isArray(newState)) { | ||
var propsDeleted: string[] = []; | ||
Object.keys(obj).forEach(key => { | ||
for (const key of Object.keys(obj)) { | ||
if (IgnoreState.isIgnoredProperty(obj, key)) | ||
return; | ||
continue; | ||
if (!newState.hasOwnProperty(key)) { | ||
// don't remove getters | ||
const desc = Object.getOwnPropertyDescriptor(obj, key); | ||
if (desc && typeof desc.get === 'function') | ||
continue; | ||
// warn when removing function properties | ||
if (typeof obj[key] === 'function') | ||
log.warn(`[updateState] Function property removed in path: ${context.path}.${key}. Consider using a method instead.`); | ||
// remove | ||
delete obj[key]; | ||
propsDeleted.push(key); | ||
} | ||
}); | ||
} | ||
// assign new state recursively | ||
var propsAssigned: string[] = []; | ||
Object.keys(newState).forEach(key => { | ||
for (const key of Object.keys(newState)) { | ||
// state of connected components is update on their source | ||
if (Connect.isConnectedProperty(obj, key)) | ||
return; | ||
if (IgnoreState.isIgnoredProperty(obj, key)) | ||
continue; | ||
// see comment about computed properties in updateState | ||
if (Computed.isComputedProperty(obj, key)) | ||
return; | ||
// don't attempt to assign get only properties | ||
const desc = Object.getOwnPropertyDescriptor(obj, key); | ||
if (desc && typeof desc.get === 'function' && typeof desc.set !== 'function') | ||
continue; | ||
if (IgnoreState.isIgnoredProperty(obj, key)) | ||
return; | ||
var subState = newState[key]; | ||
@@ -427,3 +422,3 @@ var subObj = obj[key]; | ||
} | ||
}); | ||
} | ||
@@ -430,0 +425,0 @@ // report changes |
@@ -23,5 +23,5 @@ | ||
/** | ||
* Stored on creators constructor. | ||
* Stored on component templates constructors. | ||
*/ | ||
export const CREATOR_INFO = Symbol('REDUX-APP.CREATOR_INFO'); | ||
export const COMPONENT_TEMPLATE_INFO = Symbol('REDUX-APP.COMPONENT_TEMPLATE_INFO'); | ||
/** | ||
@@ -28,0 +28,0 @@ * Stored on any class constructor. |
@@ -1,2 +0,2 @@ | ||
// tslint:disable:ban-types | ||
// tslint:disable:ban-types interface-name | ||
@@ -3,0 +3,0 @@ export type Method = Function; |
@@ -1,4 +0,3 @@ | ||
export * from './defineProperty'; | ||
export * from './log'; | ||
export * from './simpleCombineReducers'; | ||
export * from './utils'; |
@@ -6,53 +6,98 @@ import { isSymbol } from '../symbols'; | ||
export function clearProperties(obj: any): void { | ||
const keys = Object.keys(obj); | ||
for (let key of keys) { | ||
delete obj[key]; | ||
} | ||
} | ||
export enum DescriptorType { | ||
None = "None", | ||
Field = "Field", | ||
/** | ||
* Properties with getter. | ||
*/ | ||
Property = "Property", | ||
Method = "Method" | ||
} | ||
/** | ||
* Return true if 'val' is primitive. For the sake of this test 'null' and | ||
* 'undefined' are considered primitives. | ||
* Define properties of 'source' in 'target'. | ||
* @param target | ||
* @param source | ||
* @param descriptorTypes By default all properties (fields, properties, methods) are defined. | ||
* If specified will define only the specified property types. | ||
*/ | ||
export function isPrimitive(val: any): boolean { | ||
if (!val) | ||
return true; | ||
const type = typeof val; | ||
return type !== 'object' && type !== 'function'; | ||
export function defineProperties(target: object, source: object, descriptorTypes?: DescriptorType[]): object { | ||
const descriptors = getAllPropertyDescriptors(source, descriptorTypes); | ||
for (const key of Object.keys(descriptors)) { | ||
Object.defineProperty(target, key, descriptors[key]); | ||
} | ||
return target; | ||
} | ||
/** | ||
* @param obj | ||
* @param bind Whether or not to bind the returned methods to 'obj'. Default | ||
* value: false. | ||
* Get own and inherited property descriptor (except those of Object). | ||
*/ | ||
export function getMethods(obj: object | Function, bind = false): IMap<Method> { | ||
if (!obj) | ||
return undefined; | ||
export function getAllPropertyDescriptors(obj: any, descriptorTypes?: DescriptorType[]): IMap<PropertyDescriptor> { | ||
let result: IMap<PropertyDescriptor> = {}; | ||
var proto: any; | ||
if (typeof obj === 'object') { | ||
proto = Object.getPrototypeOf(obj); | ||
} else if (typeof obj === 'function') { | ||
proto = obj.prototype; | ||
} else { | ||
throw new Error("Expected an object or a function. Got: " + obj); | ||
} | ||
while (obj.constructor !== Object) { | ||
if (!proto) | ||
return undefined; | ||
// get descriptor of current type | ||
let descriptors: IMap<PropertyDescriptor> = Object.getOwnPropertyDescriptors(obj); | ||
var methods: any = {}; | ||
for (let key of Object.keys(proto)) { | ||
// filter by descriptor type | ||
if (descriptorTypes && descriptorTypes.length) { | ||
const filteredDescriptors: IMap<PropertyDescriptor> = {}; | ||
// avoid invoking getters | ||
var desc = Object.getOwnPropertyDescriptor(proto, key); | ||
var hasGetter = desc && typeof desc.get === 'function'; | ||
for (const key of Object.keys(descriptors)) { | ||
for (const flag of descriptorTypes) { | ||
let shouldAdd = false; | ||
switch (flag) { | ||
case DescriptorType.None: | ||
break; | ||
case DescriptorType.Field: | ||
shouldAdd = (typeof descriptors[key].value !== 'function' && typeof descriptors[key].get !== 'function'); | ||
break; | ||
case DescriptorType.Property: | ||
shouldAdd = (typeof descriptors[key].get === 'function'); | ||
break; | ||
case DescriptorType.Method: | ||
shouldAdd = (typeof descriptors[key].value === 'function' && typeof descriptors[key].get !== 'function'); | ||
break; | ||
default: | ||
throw new Error("Property flag not supported: " + flag); | ||
} | ||
if (!hasGetter && typeof proto[key] === 'function') { | ||
methods[key] = proto[key]; | ||
if (bind) { | ||
methods[key] = methods[key].bind(obj); | ||
if (shouldAdd) | ||
filteredDescriptors[key] = descriptors[key]; | ||
} | ||
} | ||
descriptors = filteredDescriptors; | ||
} | ||
// store in result | ||
result = Object.assign(descriptors, result); | ||
// repeat with prototype | ||
obj = getPrototype(obj); | ||
} | ||
return methods; | ||
// a "constructor" property is always retrieved as part of the result | ||
if (result.constructor) | ||
delete result.constructor; | ||
return result; | ||
} | ||
export function getConstructorProp(obj: object, key: symbol | string): any { | ||
if (!obj || !obj.constructor) | ||
return undefined; | ||
const ctor = (obj.constructor as any); | ||
return ctor[key]; | ||
} | ||
export function getConstructorOwnProp(obj: object, key: symbol | string): any { | ||
@@ -73,67 +118,37 @@ if (!obj || !obj.constructor) | ||
/** | ||
* If 'obj' is a function it's assumed to be a constructor function and returned as-is. | ||
* If 'obj' is an object it's type is returned. | ||
* Otherwise the function throws. | ||
* @param obj | ||
* @param bind Whether or not to bind the returned methods to 'obj'. Default value: false. | ||
*/ | ||
export function getType(obj: object | Function): Function { | ||
if (!obj) | ||
return undefined; | ||
// constructor function | ||
if (typeof obj === 'function') | ||
return obj; | ||
// object | ||
if (typeof obj === 'object') | ||
return Object.getPrototypeOf(obj).constructor; | ||
throw new Error("Expected an object or a function. Got: " + obj); | ||
export function getMethods(obj: object | Function, bind = false): IMap<Method> { | ||
const methodDescriptors = getAllPropertyDescriptors(obj, [DescriptorType.Method]); | ||
const methods: IMap<Method> = {}; | ||
for (const key of Object.keys(methodDescriptors)) { | ||
methods[key] = methodDescriptors[key].value; | ||
if (bind) { | ||
methods[key] = methods[key].bind(obj); | ||
} | ||
} | ||
return methods; | ||
} | ||
export function getParentType(obj: object | Function) { | ||
// get own type | ||
var type = getType(obj); | ||
// get parent type | ||
return Object.getPrototypeOf(type.prototype).constructor; | ||
export function getPrototype(obj: object | Function): object { | ||
if (typeof obj === 'object') { | ||
return Object.getPrototypeOf(obj); | ||
} else if (typeof obj === 'function') { | ||
return obj.prototype; | ||
} else { | ||
throw new Error("Expected an object or a function. Got: " + obj); | ||
} | ||
} | ||
/** | ||
* Test if an object is a plain object, i.e. is constructed by the built-in | ||
* Object constructor and inherits directly from Object.prototype or null. | ||
* | ||
* Some built-in objects pass the test, e.g. Math which is a plain object and | ||
* some host or exotic objects may pass also. | ||
* | ||
* https://stackoverflow.com/questions/5876332/how-can-i-differentiate-between-an-object-literal-other-javascript-objects | ||
* Return true if 'val' is primitive. For the sake of this test 'null' and | ||
* 'undefined' are considered primitives. | ||
*/ | ||
export function isPlainObject(obj: any) { | ||
if (!obj) | ||
return false; | ||
export function isPrimitive(val: any): boolean { | ||
if (!val) | ||
return true; | ||
if (typeof obj !== 'object') | ||
return false; | ||
// if Object.getPrototypeOf supported, use it | ||
if (typeof Object.getPrototypeOf === 'function') { | ||
var proto = Object.getPrototypeOf(obj); | ||
return proto === Object.prototype || proto === null; | ||
} | ||
// otherwise, use internal class | ||
// this should be reliable as if getPrototypeOf not supported, is pre-ES5 | ||
return Object.prototype.toString.call(obj) === '[object Object]'; | ||
} | ||
export function toPlainObject(obj: any): any { | ||
const json = JSON.stringify(obj, (key: any, value: any) => value === undefined ? null : value); | ||
return JSON.parse(json); | ||
} | ||
export function clearProperties(obj: any): void { | ||
const keys = Object.keys(obj); | ||
for (let key of keys) { | ||
delete obj[key]; | ||
} | ||
const type = typeof val; | ||
return type !== 'object' && type !== 'function'; | ||
} |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
3
246982
31
2468
418
2
- Removedlodash.isequal@4.5.0
- Removedreflect-metadata@0.1.10
- Removedlodash.isequal@4.5.0(transitive)
- Removedreflect-metadata@0.1.10(transitive)