Skip to content

Commit

Permalink
New update mathod and overall fixes
Browse files Browse the repository at this point in the history
- add update spec file

- improve update method to do not try to update when state is not dispatched yet

- add script for single tests run and all tests

- fix bugs in NucleoList dispatch

- change version in package.json

- improve README documentation

- Add commented functionality for custom primitive types

- finish and polish update functionality

- Add licence file

- Fixes in README documentation

- Create index file with all Nucleo library methods
  • Loading branch information
mtmr0x committed Oct 18, 2018
1 parent c30d6bc commit 6894171
Show file tree
Hide file tree
Showing 9 changed files with 288 additions and 39 deletions.
21 changes: 21 additions & 0 deletions LICENCE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
The MIT License

Copyright (c) Matheus Marsiglio. http://mtmr0x.com

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
31 changes: 15 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const productsContract = new NucleoObject({
const contracts = {
user: userContract,
products: productsContract
}
};
```

### Creating the store:
Expand All @@ -85,17 +85,17 @@ Dispatch function, considering user contract above:

```javascript

dispatch('user', { name: { firstName: 'John', lastName: 'Nor' } });
dispatch('user')({ name: { firstName: 'John', lastName: 'Nor' } });
// it'll fail because it's missing age field

dispatch('user', { name: { firstName: 'John', lastName: 'Nor' }, age: 27 });
dispatch('user')({ name: { firstName: 'John', lastName: 'Nor' }, age: 27 });
// it'll save the data to store properly
```

Update function, considering user contract above:

```javascript
update('user', { name: { firstName: 'John' }});
update('user')({ name: { firstName: 'John' }});
// it'll update only the user first name and only if this item has been already created in the store before
```

Expand All @@ -115,9 +115,7 @@ listener({ contractName }); // This way you can understand better what was updat

## Error management

Considering Nucleo is strongly typed and is designed to run in a client side environment, errors might be tricky to handle in how your user is inputting data and Nucleo is also designed to make sure your rules will work out and your user can make mistakes.

`dispatch` and `update` methods return an object containing the following structure:
Nucleo makes error management easy by type checking every level of contracts and returning an Object human and machine readable. The `update` and `dispatch` methods return an object with the following structure:

```javascript
{
Expand All @@ -133,29 +131,30 @@ Considering Nucleo is strongly typed and is designed to run in a client side env
}
```

Let's go deep into an example using a Nucleo custom primitive type with an inferred rule:
Code example:

```javascript
import {
NucleoString,
NucleoObject
} from 'nucleo-js';

const ageRule = (value) => {
return value < 100; // you defined here that age can not be higher than 100
}

const userType = new NucleoObject({
name: 'user',
fields: {
name: NucleoString(),
age: NucleoString(ageRule)
name: NucleoString,
}
});

const dispatcher = update('user', { age: 140 });
const contracts = {
user: userType
};

const { update } = createStore(contracts); // send contracts to create the store

const user = update('user')({ age: 140 });

console.log(dispatcher.error); // here you'll find the error below:
console.log(user.errors); // here you'll find the error below:
/*
[
Expand Down
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "super-cool-data-tool",
"version": "0.1.0",
"version": "0.2.0",
"main": "src/index.js",
"author": "Matheus Marsiglio <[email protected]>",
"license": "MIT",
Expand All @@ -17,7 +17,8 @@
"scripts": {
"start": "ts-node ./src/index.ts",
"nodemon": "nodemon",
"test": "mocha -r ts-node/register src/**/*.spec.ts",
"tests": "mocha -r ts-node/register src/**/*.spec.ts",
"test": "mocha -r ts-node/register",
"compile": "tsc"
}
}
20 changes: 20 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { createStore } from './store';

import {
NucleoString,
NucleoNumber,
NucleoBoolean
} from './nucleoTypes/primitive'

import NucleoObject from './nucleoTypes/NucleoObject';
import NucleoList from './nucleoTypes/NucleoList';

export {
createStore,
NucleoString,
NucleoNumber,
NucleoBoolean,
NucleoList,
NucleoObject
};

82 changes: 70 additions & 12 deletions src/lawyer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,90 @@ import NucleoList from './nucleoTypes/NucleoList';

import { NucleoObjectType } from './_types/NucleoObjectType';

const executeListeners = (contractName: string, listeners: Array<Function>) => {
const executeListeners = (contractName: string, listeners: Array<Function>) => {
for (let i = 0; i < listeners.length; i++) {
listeners[i]({ contractName });
}
};

const indexSearch = (contractData: any, data: any, newData:any = {}) => {
const dataKeys = Object.keys(data);
const contractDataKeys = Object.keys(contractData);

for (let i = 0; contractDataKeys.length > i; i++) {
// reflection for appending data to newData
const dataTypeReflection = () => ({
'object': () => {
// if current data is object, recusirvely call indexSearch
const bufferData = data[contractDataKeys[i]] || contractData[contractDataKeys[i]];
newData[contractDataKeys[i]] = {}
return indexSearch(contractData[contractDataKeys[i]], bufferData, newData[contractDataKeys[i]]);
},
'primitive': () => {
if (data[contractDataKeys[i]]) {
return newData[contractDataKeys[i]] = data[contractDataKeys[i]];
}
return newData[contractDataKeys[i]] = contractData[contractDataKeys[i]];
}
});
if (typeof contractData[contractDataKeys[i]] === 'object') {
dataTypeReflection()['object']();
continue;
}
dataTypeReflection()['primitive']();
}

return newData;
}

const saveMethodReflection = (store: any, contractName: string) => ({
dispatch: (data: any) => {
return store[contractName] = data;
},
update: (data: any) => {
return store[contractName] = Object.assign(store[contractName], data);
return store[contractName] = indexSearch(store[contractName], data);
}
});

export default function lawyer(contract: NucleoObjectType, data: any, saveMethod:'update'|'dispatch') {
interface LawyerInterface {
contract: NucleoObjectType;
data: any;
saveMethod: 'update'|'dispatch';
__errors__: Array<any>;
}

export default function lawyer({
contract,
data,
saveMethod,
__errors__,
}:LawyerInterface) {
const { fields: contractFields }:any = contract;
const dataKeys:Array<string> = Object.keys(data);
const contractName:string = contract.name;
let __errors__: Array<any> = [];

if (dataKeys.length !== Object.keys(contractFields).length && saveMethod === 'dispatch') {
throw Error(
`Fata error: In dispatch, the dispatched data and the contract must match in every level. For changing just few values from ${contractName} contract, use update() method.`
`Fatal error: In dispatch, the dispatched data and the contract must match in every level. For changing just few values from ${contractName} contract, use update() method.`
);
}

// loop checking object values comparison
// loop object values comparison
for (let i = 0; dataKeys.length > i; i++) {
const currentDataKey = data[dataKeys[i]];
// recursion to call itself when is NucleoObject instance
// REGION NucleoObject
if (contractFields[dataKeys[i]] instanceof NucleoObject) {
lawyer(contractFields[dataKeys[i]], currentDataKey, saveMethod);
lawyer({
contract: contractFields[dataKeys[i]],
data: currentDataKey,
saveMethod,
__errors__
});
continue;
}
// END REGION NucleoObject

// REGION NucleoList
if ((contractFields[dataKeys[i]] instanceof NucleoList) && Array.isArray(currentDataKey)) {
const _listItemType = contractFields[dataKeys[i]].getListChildrenType();
const _NucleoItemType = contractFields[dataKeys[i]][_listItemType];
Expand All @@ -59,7 +107,12 @@ export default function lawyer(contract: NucleoObjectType, data: any, saveMethod
NucleoObject: () => {
if (_NucleoItemType instanceof NucleoObject) {
for (let d = 0; d < currentDataKey.length; d++) {
lawyer(_NucleoItemType, currentDataKey[d], saveMethod);
lawyer({
contract: _NucleoItemType,
data: currentDataKey[d],
saveMethod,
__errors__
});
}
}
},
Expand All @@ -73,21 +126,24 @@ export default function lawyer(contract: NucleoObjectType, data: any, saveMethod
error: `NucleoList should receive data as list, but got ${typeof currentDataKey}`
});
}
// END REGION NucleoList

// REGION primitive types validation
if (!contractFields[dataKeys[i]]) {
__errors__.push({
contract: contractName,
error: `${dataKeys[i]} is not in ${contractName} contract and can not be saved in store.`
});
}

if (contractFields[dataKeys[i]] && !contractFields[dataKeys[i]].serialize(currentDataKey)) {
if (contractFields[dataKeys[i]] && contractFields[dataKeys[i]].serialize && !contractFields[dataKeys[i]].serialize(currentDataKey)) {
__errors__.push({
contract: contractName,
field: contractFields[dataKeys[i]],
error: `${dataKeys[i]} does not match its rules according to ${contractName} contract`
});
}
// END REGION primitive types validation
}

let operationStatus:''|'NOK'|'OK' = '';
Expand All @@ -98,8 +154,10 @@ export default function lawyer(contract: NucleoObjectType, data: any, saveMethod
}

return (store:any, listeners:Array<Function>) => {
saveMethodReflection(store, contractName)[saveMethod](data);
executeListeners(contractName, listeners);
if (!__errors__.length) {
executeListeners(contractName, listeners);
saveMethodReflection(store, contractName)[saveMethod](data);
}

return {
status: operationStatus,
Expand Down
47 changes: 47 additions & 0 deletions src/nucleoTypes/primitive.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
// TODO: choose a better name for serialize, it doesn't fit perfectly
import { NucleoPrimitiveType } from './../_types/NucleoPrimitiveType';

// class NucleoCustomPrimitive {
// Type: string;
// userFormatValidation: Function|void;
// nativeType: string;
//
// serialize(value:string|number|boolean):boolean {
// if (typeof value !== this.nativeType || this.formatValidation()) {
// return false;
// }
// return true;
// }
//
// formatValidation():boolean {
// if (this.userFormatValidation) {
// return this.userFormatValidation();
// }
// return false;
// }
// }

export const NucleoString: NucleoPrimitiveType = {
Type: 'NucleoString',
serialize: (value: string):boolean => {
Expand Down Expand Up @@ -34,3 +54,30 @@ export const NucleoBoolean: NucleoPrimitiveType = {
}
};

// export class NucleoString extends NucleoCustomPrimitive {
// constructor(userFormatValidation:Function|void) {
// super();
// this.Type = 'NucleoString';
// this.nativeType = 'string';
// this.userFormatValidation = userFormatValidation;
// }
// }
//
// export class NucleoNumber extends NucleoCustomPrimitive {
// constructor(userFormatValidation:Function|void) {
// super();
// this.Type = 'NucleoNumber';
// this.nativeType = 'number';
// this.userFormatValidation = userFormatValidation;
// }
// }
//
// export class NucleoBoolean extends NucleoCustomPrimitive {
// constructor(userFormatValidation:Function|void) {
// super();
// this.Type = 'NucleoBoolean';
// this.nativeType = 'boolean';
// this.userFormatValidation = userFormatValidation;
// }
// }

16 changes: 14 additions & 2 deletions src/save.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,23 @@ export default function save({
return (contractName: string) => {
if (!contracts[contractName]) {
throw Error(
`The provided contract named as "${contractName}" could not be found in store contracts`
`Fatal error: The provided contract named as "${contractName}" could not be found in store contracts`
);
}

if (saveMethod === 'update' && !store[contractName]) {
throw Error(
`Fatal error: you can not update an item in store if it is not created yet.
First use dispatch to save it and then you can perform updates at ${contractName} contract.`
);
}
return (data: any) => {
return lawyer({ name: contractName, fields: contracts[contractName]}, data, saveMethod)(store, listeners);
return lawyer({
contract: { name: contractName, fields: contracts[contractName]},
data,
saveMethod,
__errors__: []
})(store, listeners);
};
}
};
Expand Down
Loading

0 comments on commit 6894171

Please sign in to comment.