Comparing version 6.5.0 to 7.0.0
CHANGELOG | ||
=== | ||
6.5.0 | ||
7.0.0 | ||
-- | ||
Set | ||
Fixed | ||
- | ||
Error for throw cases instead of simple message for better call stack in those cases. | ||
Typescript 4.8.3 state type constraints. | ||
* removed IState constraint in CursorType, | ||
* fixed Object type check for shallowCopy. | ||
@@ -11,0 +13,0 @@ 6.4.0 |
@@ -0,0 +0,0 @@ import * as s from './src/store'; |
{ | ||
"name": "fun-model", | ||
"version": "6.5.0", | ||
"version": "7.0.0", | ||
"description": "fun-model is pure functional implementation of FLUX architecture.", | ||
@@ -34,4 +34,8 @@ "main": "./index.js", | ||
}, | ||
"prettier": { | ||
"tabWidth": 4, | ||
"singleQuote": true | ||
}, | ||
"devDependencies": {}, | ||
"license": "MIT" | ||
} |
@@ -0,0 +0,0 @@ # FUN-Model |
@@ -7,3 +7,3 @@ import * as s from '../src/store'; | ||
describe('actionFactory', () => { | ||
let debugCallback: d.debugCallbackType | ||
let debugCallback: d.debugCallbackType; | ||
let renderCallback: () => void; | ||
@@ -23,3 +23,6 @@ | ||
expect(debugCallback).toHaveBeenCalledWith('Action factory has been initialized.', undefined); | ||
expect(debugCallback).toHaveBeenCalledWith( | ||
'Action factory has been initialized.', | ||
undefined | ||
); | ||
}); | ||
@@ -35,4 +38,7 @@ }); | ||
givenTodosStore({ | ||
todos: [{ done: false, name: 'First todo' }, { done: false, name: 'Second todo' }], | ||
nullableNumber: null | ||
todos: [ | ||
{ done: false, name: 'First todo' }, | ||
{ done: false, name: 'Second todo' }, | ||
], | ||
nullableNumber: null, | ||
}); | ||
@@ -44,7 +50,12 @@ | ||
return { key: `todos.${params.index}` }; | ||
} | ||
}, | ||
}, | ||
(_state: tds.ITodo, params: tds.ITodoParams) => { return params.todo } | ||
(_state: tds.ITodo, params: tds.ITodoParams) => { | ||
return params.todo; | ||
} | ||
); | ||
testAction({ index: 1, todo: { done: false, name: 'New second todo' } }); | ||
testAction({ | ||
index: 1, | ||
todo: { done: false, name: 'New second todo' }, | ||
}); | ||
@@ -57,7 +68,19 @@ expect(getTodos()[1].done).toBeFalsy(); | ||
givenTodosStore({ | ||
todos: [{ done: false, name: 'First todo' }, { done: false, name: 'Second todo' }], | ||
nullableNumber: null | ||
todos: [ | ||
{ done: false, name: 'First todo' }, | ||
{ done: false, name: 'Second todo' }, | ||
], | ||
nullableNumber: null, | ||
}); | ||
let testAction = af.createAction<boolean, number>({ create: (index) => { return { key: `todos.${index}.done` } } }, () => { return true; }); | ||
let testAction = af.createAction<boolean, number>( | ||
{ | ||
create: (index) => { | ||
return { key: `todos.${index}.done` }; | ||
}, | ||
}, | ||
() => { | ||
return true; | ||
} | ||
); | ||
testAction(1); | ||
@@ -82,5 +105,8 @@ | ||
af.bootstrap(renderCallback, false); | ||
throwingAction = af.createParamLessAction(NestedCursorTestFixture, () => { | ||
throw 'MyDummyException'; | ||
}); | ||
throwingAction = af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
() => { | ||
throw 'MyDummyException'; | ||
} | ||
); | ||
}); | ||
@@ -97,5 +123,8 @@ | ||
af.bootstrap(renderCallback, true); | ||
throwingAction = af.createParamLessAction(NestedCursorTestFixture, () => { | ||
throw 'MyDummyException'; | ||
}); | ||
throwingAction = af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
() => { | ||
throw 'MyDummyException'; | ||
} | ||
); | ||
}); | ||
@@ -110,3 +139,6 @@ | ||
expect(debugCallback).toHaveBeenCalledWith('Action factory has been initialized.', undefined); | ||
expect(debugCallback).toHaveBeenCalledWith( | ||
'Action factory has been initialized.', | ||
undefined | ||
); | ||
}); | ||
@@ -117,3 +149,6 @@ }); | ||
it('does not throw if action has been only declared.', () => { | ||
let testAction = af.createAction(NestedCursorTestFixture, (state: INestedState) => state); | ||
let testAction = af.createAction( | ||
NestedCursorTestFixture, | ||
(state: INestedState) => state | ||
); | ||
expect(testAction).not.toBeUndefined(); | ||
@@ -124,5 +159,12 @@ }); | ||
expect(() => { | ||
let testAction = af.createParamLessAction(NestedCursorTestFixture, () => { return { state: 'new nested state' }; }); | ||
let testAction = af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
() => { | ||
return { state: 'new nested state' }; | ||
} | ||
); | ||
testAction(); | ||
}).toThrowError('Render callback must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).'); | ||
}).toThrowError( | ||
'Render callback must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).' | ||
); | ||
}); | ||
@@ -142,5 +184,11 @@ }); | ||
af.createParamLessAction(NestedCursorTestFixture, (state: INestedState) => state)(); | ||
af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
(state: INestedState) => state | ||
)(); | ||
expect(debugCallback).not.toHaveBeenCalledWith('Global state has been changed.', undefined); | ||
expect(debugCallback).not.toHaveBeenCalledWith( | ||
'Global state has been changed.', | ||
undefined | ||
); | ||
}); | ||
@@ -152,5 +200,11 @@ | ||
af.createParamLessAction(NestedCursorTestFixture, () => newState)(); | ||
af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
() => newState | ||
)(); | ||
expect(debugCallback).toHaveBeenCalledWith('Global state has been changed.', undefined); | ||
expect(debugCallback).toHaveBeenCalledWith( | ||
'Global state has been changed.', | ||
undefined | ||
); | ||
}); | ||
@@ -161,3 +215,6 @@ | ||
let testAction = af.createParamLessAction(NestedCursorTestFixture, (state: INestedState) => state); | ||
let testAction = af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
(state: INestedState) => state | ||
); | ||
testAction(); | ||
@@ -171,3 +228,8 @@ | ||
let testAction = af.createParamLessAction(NestedCursorTestFixture, () => { return { state: 'newValue' }; }) | ||
let testAction = af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
() => { | ||
return { state: 'newValue' }; | ||
} | ||
); | ||
testAction(); | ||
@@ -181,10 +243,20 @@ | ||
const nestedAction2 = af.createParamLessAction(NestedCursorTestFixture, (state: INestedState) => { | ||
return { state: `${state.state} -> newValueFromNestedAction2` }; | ||
}); | ||
const nestedAction2 = af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
(state: INestedState) => { | ||
return { | ||
state: `${state.state} -> newValueFromNestedAction2`, | ||
}; | ||
} | ||
); | ||
const nestedAction = af.createParamLessAction(NestedCursorTestFixture, (state: INestedState) => { | ||
nestedAction2(); | ||
return { state: `${state.state} -> newValueFromNestedAction` }; | ||
}); | ||
const nestedAction = af.createParamLessAction( | ||
NestedCursorTestFixture, | ||
(state: INestedState) => { | ||
nestedAction2(); | ||
return { | ||
state: `${state.state} -> newValueFromNestedAction`, | ||
}; | ||
} | ||
); | ||
@@ -196,4 +268,5 @@ af.createParamLessAction(NestedCursorTestFixture, () => { | ||
expect(s.getState(NestedCursorTestFixture).state) | ||
.toBe('newValue -> newValueFromNestedAction -> newValueFromNestedAction2'); | ||
expect(s.getState(NestedCursorTestFixture).state).toBe( | ||
'newValue -> newValueFromNestedAction -> newValueFromNestedAction2' | ||
); | ||
}); | ||
@@ -204,6 +277,9 @@ | ||
givenStore(aState('nestedStateValue')); | ||
const testAction = af.createParamLessAction(SomeCursorTestFixture, (state: ISomeState) => { | ||
state.nested.state = state.nested.state + 'newValue'; | ||
return state; | ||
}); | ||
const testAction = af.createParamLessAction( | ||
SomeCursorTestFixture, | ||
(state: ISomeState) => { | ||
state.nested.state = state.nested.state + 'newValue'; | ||
return state; | ||
} | ||
); | ||
@@ -223,3 +299,5 @@ expect(() => { | ||
givenStore(aState('nestedStateValue')); | ||
const testAction = af.createReplaceAction<string>(NestedStateCursorTestFixture); | ||
const testAction = af.createReplaceAction<string>( | ||
NestedStateCursorTestFixture | ||
); | ||
@@ -232,3 +310,5 @@ testAction('newNestedStateValue'); | ||
givenStore(aState('nestedStateValue')); | ||
const testAction = af.createReplaceAction<string>(NestedStateCursorTestFixture); | ||
const testAction = af.createReplaceAction<string>( | ||
NestedStateCursorTestFixture | ||
); | ||
@@ -240,17 +320,25 @@ testAction('nestedStateValue'); | ||
givenStore(aState('nestedStateValue')); | ||
const testAction = af.createReplaceAction<string>(NestedStateCursorTestFixture); | ||
const testAction = af.createReplaceAction<string>( | ||
NestedStateCursorTestFixture | ||
); | ||
testAction('newNestedStateValue'); | ||
expect(s.getState(NestedStateCursorTestFixture)).toBe('newNestedStateValue'); | ||
expect(s.getState(NestedStateCursorTestFixture)).toBe( | ||
'newNestedStateValue' | ||
); | ||
}); | ||
it('does not replace the same state', () => { | ||
givenStore(aState('nestedStateValue')); | ||
const testAction = af.createReplaceAction<string>(NestedStateCursorTestFixture); | ||
const testAction = af.createReplaceAction<string>( | ||
NestedStateCursorTestFixture | ||
); | ||
testAction('nestedStateValue'); | ||
expect(s.getState(NestedStateCursorTestFixture)).toBe('nestedStateValue'); | ||
expect(s.getState(NestedStateCursorTestFixture)).toBe( | ||
'nestedStateValue' | ||
); | ||
}); | ||
}) | ||
}); | ||
@@ -263,6 +351,8 @@ describe('createActions', () => { | ||
cursor: NestedCursorTestFixture, | ||
handler: (state: INestedState): INestedState => state | ||
handler: (state: INestedState): INestedState => state, | ||
}); | ||
testAction(); | ||
}).toThrowError('Render callback must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).'); | ||
}).toThrowError( | ||
'Render callback must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).' | ||
); | ||
}); | ||
@@ -285,7 +375,9 @@ }); | ||
cursor: NestedCursorTestFixture, | ||
handler: (state: INestedState): INestedState => state | ||
}, { | ||
handler: (state: INestedState): INestedState => state, | ||
}, | ||
{ | ||
cursor: NestedCursorTestFixture, | ||
handler: (state: INestedState): INestedState => state | ||
}); | ||
handler: (state: INestedState): INestedState => state, | ||
} | ||
); | ||
testAction(); | ||
@@ -299,11 +391,19 @@ | ||
let testAction = af.createParamLessActions( | ||
let testAction = af.createParamLessActions<any>( | ||
{ | ||
cursor: SomeCursorTestFixture, | ||
handler: (state: ISomeState): ISomeState => { return { nested: state.nested, state: 'newStateValue' } } | ||
handler: (state: ISomeState): ISomeState => { | ||
return { | ||
nested: state.nested, | ||
state: 'newStateValue', | ||
}; | ||
}, | ||
}, | ||
{ | ||
cursor: NestedCursorTestFixture, | ||
handler: (): INestedState => { return { state: 'newNestedStateValue' }; } | ||
}); | ||
handler: (): INestedState => { | ||
return { state: 'newNestedStateValue' }; | ||
}, | ||
} | ||
); | ||
testAction(); | ||
@@ -325,3 +425,6 @@ | ||
function aState(nestedState: string = 'aNestedState', state: string = 'aState'): IStateTestFixture { | ||
function aState( | ||
nestedState: string = 'aNestedState', | ||
state: string = 'aState' | ||
): IStateTestFixture { | ||
return { some: { nested: { state: nestedState }, state: state } }; | ||
@@ -335,3 +438,3 @@ } | ||
interface ISomeState extends s.IState { | ||
nested: INestedState | ||
nested: INestedState; | ||
state: string; | ||
@@ -345,11 +448,11 @@ } | ||
var NestedCursorTestFixture: s.ICursor<INestedState> = { | ||
key: 'some.nested' | ||
} | ||
key: 'some.nested', | ||
}; | ||
var NestedStateCursorTestFixture: s.ICursor<string> = { | ||
key: 'some.nested.state' | ||
} | ||
key: 'some.nested.state', | ||
}; | ||
var SomeCursorTestFixture: s.ICursor<ISomeState> = { | ||
key: 'some' | ||
} | ||
key: 'some', | ||
}; |
@@ -13,4 +13,4 @@ import * as h from '../src/helpers'; | ||
subObject: { | ||
id: 'anSubId' | ||
} | ||
id: 'anSubId', | ||
}, | ||
}; | ||
@@ -42,3 +42,3 @@ }); | ||
it('sets properties in callback without return', () => { | ||
let newState = h.shallowCopy(aState, s => { | ||
let newState = h.shallowCopy(aState, (s) => { | ||
s.id = 'newId'; | ||
@@ -53,7 +53,9 @@ s.subObject = { id: 'newSubId' }; | ||
it('sets properties in nested shallowCopy', () => { | ||
let newState = h.shallowCopy(aState, s => h.shallowCopy(s, a => { | ||
a.id = 'newId'; | ||
a.subObject = { id: 'newSubId' }; | ||
return a; | ||
})); | ||
let newState = h.shallowCopy(aState, (s) => | ||
h.shallowCopy(s, (a) => { | ||
a.id = 'newId'; | ||
a.subObject = { id: 'newSubId' }; | ||
return a; | ||
}) | ||
); | ||
@@ -65,3 +67,3 @@ expect(newState.id).toBe('newId'); | ||
it('sets properties in inline style', () => { | ||
let newState = h.shallowCopy(aState, s => { | ||
let newState = h.shallowCopy(aState, (s) => { | ||
s.id = 'newId'; | ||
@@ -80,3 +82,3 @@ s.subObject = { id: 'newSubId' }; | ||
beforeEach(() => { | ||
newState = h.shallowCopy(aState, ns => { | ||
newState = h.shallowCopy(aState, (ns) => { | ||
ns.list = h.shallowCopy(ns.list); | ||
@@ -96,7 +98,10 @@ }); | ||
for (let key in newState.list) { | ||
if (newState.list.hasOwnProperty(key) && typeof key !== 'function') { | ||
if ( | ||
newState.list.hasOwnProperty(key) && | ||
typeof key !== 'function' | ||
) { | ||
expect(newState.list[key]).toBe(aState.list[key]); | ||
} | ||
} | ||
}) | ||
}); | ||
}); | ||
@@ -126,3 +131,6 @@ | ||
it('respects returned value', () => { | ||
const returnValue = h.shallowCopy('test', (_original: string) => 'changed value'); | ||
const returnValue = h.shallowCopy( | ||
'test', | ||
(_original: string) => 'changed value' | ||
); | ||
@@ -142,7 +150,7 @@ expect(returnValue).toBe('changed value'); | ||
subObject: { | ||
id: 'anSubId' | ||
} | ||
id: 'anSubId', | ||
}, | ||
}; | ||
h.deepFreeze(state); | ||
}) | ||
}); | ||
@@ -160,9 +168,9 @@ it('freezes root object', () => { | ||
}); | ||
}) | ||
}); | ||
}); | ||
interface IDummyState { | ||
id: string | ||
list: number[] | ||
subObject: any | ||
} | ||
id: string; | ||
list: number[]; | ||
subObject: any; | ||
} |
@@ -40,3 +40,5 @@ import * as s from '../src/store'; | ||
it('returns false when value does not exist in state through cursor', () => { | ||
const exists = s.isExistingCursor({ key: 'some.nested.notExistingState' }); | ||
const exists = s.isExistingCursor({ | ||
key: 'some.nested.notExistingState', | ||
}); | ||
@@ -48,7 +50,11 @@ expect(exists).toBeFalsy(); | ||
beforeEach(() => { | ||
s.bootstrap({ some: { nested: { arrayState: ['value1', 'value2'] } } }); | ||
s.bootstrap({ | ||
some: { nested: { arrayState: ['value1', 'value2'] } }, | ||
}); | ||
}); | ||
it('returns true when value exists in state through cursor', () => { | ||
const exists = s.isExistingCursor({ key: 'some.nested.arrayState.0' }); | ||
const exists = s.isExistingCursor({ | ||
key: 'some.nested.arrayState.0', | ||
}); | ||
@@ -59,3 +65,5 @@ expect(exists).toBeTruthy(); | ||
it('returns false when value does not exist in state through cursor', () => { | ||
const exists = s.isExistingCursor({ key: 'some.nested.arrayState.5' }); | ||
const exists = s.isExistingCursor({ | ||
key: 'some.nested.arrayState.5', | ||
}); | ||
@@ -70,4 +78,5 @@ expect(exists).toBeFalsy(); | ||
it('throws if key does not exist', () => { | ||
expect(() => s.getState<s.IState>(s.rootCursor)) | ||
.toThrowError('Default state must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).'); | ||
expect(() => s.getState<s.IState>(s.rootCursor)).toThrowError( | ||
'Default state must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).' | ||
); | ||
}); | ||
@@ -98,4 +107,7 @@ }); | ||
it('throws if key does not exist', () => { | ||
expect(() => s.getState<s.IState>({ key: 'notExistingKey' })) | ||
.toThrowError('State for cursor key (notExistingKey) does not exist.'); | ||
expect(() => | ||
s.getState<s.IState>({ key: 'notExistingKey' }) | ||
).toThrowError( | ||
'State for cursor key (notExistingKey) does not exist.' | ||
); | ||
}); | ||
@@ -106,6 +118,9 @@ | ||
const state = s.getState({ key: 'some.nested.optional', isUndefinable: true }); | ||
const state = s.getState({ | ||
key: 'some.nested.optional', | ||
isUndefinable: true, | ||
}); | ||
expect(state).toBeUndefined(); | ||
}) | ||
}); | ||
}); | ||
@@ -120,4 +135,7 @@ | ||
givenTodoStore({ | ||
todos: [{ done: false, name: 'First Todo' }, { done: false, name: 'Second Todo' }], | ||
nullableNumber: null | ||
todos: [ | ||
{ done: false, name: 'First Todo' }, | ||
{ done: false, name: 'Second Todo' }, | ||
], | ||
nullableNumber: null, | ||
}); | ||
@@ -133,3 +151,3 @@ | ||
todos: [{ done: false, name: 'First Todo' }], | ||
nullableNumber: null | ||
nullableNumber: null, | ||
}); | ||
@@ -151,4 +169,5 @@ | ||
it('throws if key does not exist', () => { | ||
expect(() => s.setState(s.rootCursor, {})) | ||
.toThrowError('Default state must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).'); | ||
expect(() => s.setState(s.rootCursor, {})).toThrowError( | ||
'Default state must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).' | ||
); | ||
}); | ||
@@ -159,3 +178,3 @@ }); | ||
const rootCursorTestFixture: s.ICursor<IStateTestFixture> = { | ||
key: '' | ||
key: '', | ||
}; | ||
@@ -169,4 +188,3 @@ | ||
const cursor = { key: 'invalid' }; | ||
expect(() => s.setState(cursor, {})) | ||
.toThrowError(); | ||
expect(() => s.setState(cursor, {})).toThrowError(); | ||
}); | ||
@@ -178,3 +196,6 @@ | ||
expect(s.getState(s.rootCursor)).toEqual({ key: null, 'invalid': {} }); | ||
expect(s.getState(s.rootCursor)).toEqual({ | ||
key: null, | ||
invalid: {}, | ||
}); | ||
}); | ||
@@ -186,3 +207,6 @@ | ||
expect(s.getState(s.rootCursor)).toEqual({ key: null, 'not': { 'existing': { key: {} } } }); | ||
expect(s.getState(s.rootCursor)).toEqual({ | ||
key: null, | ||
not: { existing: { key: {} } }, | ||
}); | ||
}); | ||
@@ -204,3 +228,5 @@ | ||
expect((s.getState(rootCursorTestFixture)).some.nested.state).toBe('newValue'); | ||
expect( | ||
s.getState(rootCursorTestFixture).some.nested.state | ||
).toBe('newValue'); | ||
}); | ||
@@ -213,3 +239,5 @@ | ||
expect((s.getState(rootCursorTestFixture)).some.nested.state).toBe('newValue'); | ||
expect( | ||
s.getState(rootCursorTestFixture).some.nested.state | ||
).toBe('newValue'); | ||
}); | ||
@@ -232,3 +260,6 @@ | ||
expect(debugCallback).toHaveBeenCalledWith('Current state:', s.getState(s.rootCursor)); | ||
expect(debugCallback).toHaveBeenCalledWith( | ||
'Current state:', | ||
s.getState(s.rootCursor) | ||
); | ||
}); | ||
@@ -251,3 +282,6 @@ | ||
s.setState({ key: 'some.nested.optional', isUndefinable: true }, 'newValue'); | ||
s.setState( | ||
{ key: 'some.nested.optional', isUndefinable: true }, | ||
'newValue' | ||
); | ||
@@ -266,4 +300,7 @@ const newState = s.getState(rootCursorTestFixture); | ||
givenTodoStore({ | ||
todos: [{ done: false, name: 'First Todo' }, { done: false, name: 'Second Todo' }], | ||
nullableNumber: null | ||
todos: [ | ||
{ done: false, name: 'First Todo' }, | ||
{ done: false, name: 'Second Todo' }, | ||
], | ||
nullableNumber: null, | ||
}); | ||
@@ -273,3 +310,5 @@ | ||
expect(s.getState({ key: 'todos.1.name' })).toBe('New Todo Name'); | ||
expect(s.getState({ key: 'todos.1.name' })).toBe( | ||
'New Todo Name' | ||
); | ||
}); | ||
@@ -280,15 +319,24 @@ | ||
todos: [{ done: false, name: 'First Todo' }], | ||
nullableNumber: null | ||
nullableNumber: null, | ||
}); | ||
s.setState({ key: 'todos.0' }, { done: false, name: 'Second Todo' }); | ||
s.setState( | ||
{ key: 'todos.0' }, | ||
{ done: false, name: 'Second Todo' } | ||
); | ||
expect(s.getState({ key: 'todos.0' })).toEqual({ done: false, name: 'Second Todo' }); | ||
expect(s.getState({ key: 'todos.0' })).toEqual({ | ||
done: false, | ||
name: 'Second Todo', | ||
}); | ||
}); | ||
it('sets new instance of array when nested item has been changed', () => { | ||
const storedTodos = [{ done: false, name: 'First Todo' }, { done: false, name: 'Second Todo' }]; | ||
const storedTodos = [ | ||
{ done: false, name: 'First Todo' }, | ||
{ done: false, name: 'Second Todo' }, | ||
]; | ||
givenTodoStore({ | ||
todos: storedTodos, | ||
nullableNumber: null | ||
nullableNumber: null, | ||
}); | ||
@@ -304,6 +352,9 @@ | ||
todos: [{ done: false, name: 'First Todo' }], | ||
nullableNumber: null | ||
nullableNumber: null, | ||
}); | ||
s.setState(tds.todosCursor, [{ done: false, name: 'Second Todo' }, { done: false, name: 'Third Todo' }]); | ||
s.setState(tds.todosCursor, [ | ||
{ done: false, name: 'Second Todo' }, | ||
{ done: false, name: 'Third Todo' }, | ||
]); | ||
@@ -340,3 +391,3 @@ expect(s.getState(tds.todosCursor).length).toBe(2); | ||
todos: [{ done: false, name: 'First Todo' }], | ||
nullableNumber: 10 | ||
nullableNumber: 10, | ||
}); | ||
@@ -365,4 +416,4 @@ | ||
optional?: string; | ||
} | ||
}; | ||
}; | ||
} |
@@ -11,22 +11,22 @@ import * as s from '../src/store'; | ||
todos: [], | ||
nullableNumber: null | ||
} | ||
} | ||
nullableNumber: null, | ||
}; | ||
}; | ||
export interface ITodo { | ||
done: boolean | ||
name: string | ||
done: boolean; | ||
name: string; | ||
} | ||
export interface ITodoParams { | ||
todo: ITodo | ||
index: number | ||
todo: ITodo; | ||
index: number; | ||
} | ||
export const todosCursor: s.ICursor<ITodo[]> = { | ||
key: 'todos' | ||
} | ||
key: 'todos', | ||
}; | ||
export const nullableNumberCursor: s.ICursor<number | null> = { | ||
key: 'nullableNumber' | ||
} | ||
key: 'nullableNumber', | ||
}; |
@@ -8,3 +8,6 @@ import * as s from './store'; | ||
export const bootstrap = (onStateChanged: (() => void) | null, withExceptionHandling: boolean | (() => boolean) = false) => { | ||
export const bootstrap = ( | ||
onStateChanged: (() => void) | null, | ||
withExceptionHandling: boolean | (() => boolean) = false | ||
) => { | ||
stateChanged = onStateChanged; | ||
@@ -24,3 +27,6 @@ queueOfHandlers = []; | ||
export type IActionHandler<TState, TParams> = (state: TState, t: TParams) => TState; | ||
export type IActionHandler<TState, TParams> = ( | ||
state: TState, | ||
t: TParams | ||
) => TState; | ||
@@ -31,6 +37,9 @@ export type IParamLessActionHandler<TState> = (state: TState) => TState; | ||
const renderCallbackMustBeSetBefore = 'Render callback must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).'; | ||
const renderCallbackMustBeSetBefore = | ||
'Render callback must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).'; | ||
export const createAction = <TState, TParams>(cursor: s.CursorType<TState> | s.ICursorFactory<TState, TParams>, handler: IActionHandler<TState, TParams>) | ||
: IAction<TParams> => { | ||
export const createAction = <TState, TParams>( | ||
cursor: s.CursorType<TState> | s.ICursorFactory<TState, TParams>, | ||
handler: IActionHandler<TState, TParams> | ||
): IAction<TParams> => { | ||
return (params: TParams): void => { | ||
@@ -40,3 +49,7 @@ if (stateChanged === null) | ||
if (changeStateWithQueue(unifyCursor(cursor, params), (state) => handler(state, params))) { | ||
if ( | ||
changeStateWithQueue(unifyCursor(cursor, params), (state) => | ||
handler(state, params) | ||
) | ||
) { | ||
stateChanged(); | ||
@@ -48,9 +61,12 @@ d.log('Rendering invoked...'); | ||
export const createReplaceAction = <TState>(cursor: s.CursorType<TState> | s.ICursorFactory<TState, TState>) | ||
: IAction<TState> => { | ||
export const createReplaceAction = <TState>( | ||
cursor: s.CursorType<TState> | s.ICursorFactory<TState, TState> | ||
): IAction<TState> => { | ||
return createAction(cursor, (_state, params) => params); | ||
}; | ||
export const createParamLessAction = <TState>(cursor: s.CursorType<TState> | s.ICursorFactory<TState, TState>, handler: IParamLessActionHandler<TState>) | ||
: IParamLessAction => { | ||
export const createParamLessAction = <TState>( | ||
cursor: s.CursorType<TState> | s.ICursorFactory<TState, TState>, | ||
handler: IParamLessActionHandler<TState> | ||
): IParamLessAction => { | ||
return (): void => { | ||
@@ -67,6 +83,13 @@ if (stateChanged === null) | ||
function unifyCursor<TState, TParams>(cursor: s.CursorType<TState> | s.ICursorFactory<TState, TParams>, params: TParams): s.ICursor<TState> { | ||
function unifyCursor<TState, TParams>( | ||
cursor: s.CursorType<TState> | s.ICursorFactory<TState, TParams>, | ||
params: TParams | ||
): s.ICursor<TState> { | ||
return (<any>cursor).create instanceof Function | ||
? <s.ICursor<TState>>(<any>cursor).create(params) | ||
: <s.ICursor<TState>>(s.isCursorFunction(<s.CursorType<TState>>cursor) ? (<() => s.ICursor<TState>>cursor)() : cursor); | ||
: <s.ICursor<TState>>( | ||
(s.isCursorFunction(<s.CursorType<TState>>cursor) | ||
? (<() => s.ICursor<TState>>cursor)() | ||
: cursor) | ||
); | ||
} | ||
@@ -76,6 +99,8 @@ | ||
cursor: s.ICursor<TState>; | ||
handler: (state: TState, t: TParam) => TState | ||
handler: (state: TState, t: TParam) => TState; | ||
} | ||
export const createActions = <TState, TParams>(...pairs: IPair<TState, TParams>[]): IAction<TParams> => { | ||
export const createActions = <TState, TParams>( | ||
...pairs: IPair<TState, TParams>[] | ||
): IAction<TParams> => { | ||
return (params: TParams) => { | ||
@@ -89,3 +114,7 @@ if (stateChanged === null) | ||
let pair = pairs[i]; | ||
if (changeStateWithQueue(pair.cursor, (state) => pair.handler(state, params))) | ||
if ( | ||
changeStateWithQueue(pair.cursor, (state) => | ||
pair.handler(state, params) | ||
) | ||
) | ||
changed = true; | ||
@@ -99,6 +128,8 @@ } | ||
cursor: s.ICursor<TState>; | ||
handler: (state: TState) => TState | ||
handler: (state: TState) => TState; | ||
} | ||
export const createParamLessActions = <TState>(...pairs: IParamLessPair<TState>[]): IParamLessAction => { | ||
export const createParamLessActions = <TState>( | ||
...pairs: IParamLessPair<TState>[] | ||
): IParamLessAction => { | ||
return () => { | ||
@@ -124,13 +155,19 @@ if (stateChanged === null) | ||
let queueOfHandlers: IQueuedHandling<any>[] = []; | ||
function changeStateWithQueue<TState>(cursor: s.ICursor<TState>, handler: IInternalActionHandler<TState>) | ||
: boolean { | ||
function changeStateWithQueue<TState>( | ||
cursor: s.ICursor<TState>, | ||
handler: IInternalActionHandler<TState> | ||
): boolean { | ||
queueOfHandlers.push({ cursor, handler }); | ||
if (queueOfHandlers.length > 1) | ||
return false; | ||
if (queueOfHandlers.length > 1) return false; | ||
let isStateChanged = false; | ||
while (queueOfHandlers.length > 0) { | ||
let n = queueOfHandlers[0]; | ||
if (h.isFunction(exceptionHandling) ? exceptionHandling() : exceptionHandling) | ||
if ( | ||
h.isFunction(exceptionHandling) | ||
? exceptionHandling() | ||
: exceptionHandling | ||
) | ||
try { | ||
isStateChanged = changeState(n.cursor, n.handler) || isStateChanged; | ||
isStateChanged = | ||
changeState(n.cursor, n.handler) || isStateChanged; | ||
} catch (error) { | ||
@@ -148,10 +185,11 @@ d.log('Error in action handling: ', error); | ||
function changeState<TState>(cursor: s.ICursor<TState>, handler: IInternalActionHandler<TState>) | ||
: boolean { | ||
function changeState<TState>( | ||
cursor: s.ICursor<TState>, | ||
handler: IInternalActionHandler<TState> | ||
): boolean { | ||
let oldState = s.getState(cursor); | ||
let newState = handler(oldState); | ||
if (oldState === newState) | ||
return false; | ||
if (oldState === newState) return false; | ||
s.setState(cursor, newState); | ||
return true; | ||
} |
@@ -0,4 +1,3 @@ | ||
export type debugCallbackType = (message: string, params?: any) => void; | ||
export type debugCallbackType = (message: string, params?: any) => void | ||
export let debug: debugCallbackType | undefined = undefined; | ||
@@ -12,2 +11,2 @@ | ||
debug && debug(message, params); | ||
} | ||
}; |
@@ -1,4 +0,7 @@ | ||
import { debug } from "./debug"; | ||
import { debug } from './debug'; | ||
export function shallowCopy<T>(source: T, callback?: (target: T) => void | T): T { | ||
export function shallowCopy<T>( | ||
source: T, | ||
callback?: (target: T) => void | T | ||
): T { | ||
if (source instanceof Array) { | ||
@@ -9,9 +12,14 @@ source = [...source] as unknown as T; | ||
} | ||
if (typeof source === "object") { | ||
return objectShallowCopy(source, callback); | ||
if (isObjectType(source)) { | ||
return objectShallowCopy<T & Object>( | ||
source, | ||
callback ? (t) => callback(t) as T & Object : undefined | ||
); | ||
} | ||
if (debug) { | ||
debug("Shallow copy should not be used for primitive types."); | ||
debug('Shallow copy should not be used for primitive types.'); | ||
} | ||
const result = callback && callback(source); | ||
@@ -21,3 +29,10 @@ return result || source; | ||
export function objectShallowCopy<T extends Object>(source: T, callback: (target: T) => void | T = (_t: T) => { }): T { | ||
function isObjectType(source: unknown): source is Object { | ||
return typeof source === 'object'; | ||
} | ||
export function objectShallowCopy<T extends Object>( | ||
source: T, | ||
callback: (target: T) => void | T = (_t: T) => {} | ||
): T { | ||
const target = <T>{}; | ||
@@ -30,3 +45,3 @@ for (var property in source) | ||
return <T>result || target; | ||
}; | ||
} | ||
@@ -37,6 +52,9 @@ export function deepFreeze<T extends Object>(source: T): T { | ||
const sourceProperty = (<any>source)[property]; | ||
if (source.hasOwnProperty(property) | ||
&& sourceProperty !== null | ||
&& (typeof sourceProperty === "object" || typeof sourceProperty === "function") | ||
&& !Object.isFrozen(sourceProperty)) | ||
if ( | ||
source.hasOwnProperty(property) && | ||
sourceProperty !== null && | ||
(typeof sourceProperty === 'object' || | ||
typeof sourceProperty === 'function') && | ||
!Object.isFrozen(sourceProperty) | ||
) | ||
deepFreeze(sourceProperty); | ||
@@ -46,6 +64,6 @@ } | ||
return source; | ||
}; | ||
} | ||
export function isFunction(val: any): val is Function { | ||
return typeof val == "function"; | ||
} | ||
return typeof val == 'function'; | ||
} |
155
src/store.ts
import * as h from './helpers'; | ||
import * as d from './debug'; | ||
export interface IState { | ||
} | ||
export interface IState {} | ||
export type CursorType<TState extends IState> = (() => ICursor<TState>) | ICursor<TState>; | ||
export function createNestedCursor<TState extends IState, TNestedState extends IState>(cursor: CursorType<TState>, nestedStateKey: string): CursorType<TNestedState> { | ||
export type CursorType<TState> = (() => ICursor<TState>) | ICursor<TState>; | ||
export function createNestedCursor< | ||
TState extends IState, | ||
TNestedState extends IState | ||
>( | ||
cursor: CursorType<TState>, | ||
nestedStateKey: string | ||
): CursorType<TNestedState> { | ||
if (isCursorFunction(cursor)) | ||
return () => { return { key: cursor().key + stateSeparator + nestedStateKey } } | ||
else | ||
return { key: cursor.key + stateSeparator + nestedStateKey }; | ||
return () => { | ||
return { key: cursor().key + stateSeparator + nestedStateKey }; | ||
}; | ||
else return { key: cursor.key + stateSeparator + nestedStateKey }; | ||
} | ||
export function createNestedCursorFactory<TRootState extends IState, TNestedState extends IState>(key: string) { | ||
return (cursor: CursorType<TRootState>) => createNestedCursor<TRootState, TNestedState>(cursor, key); | ||
export function createNestedCursorFactory< | ||
TRootState extends IState, | ||
TNestedState extends IState | ||
>(key: string) { | ||
return (cursor: CursorType<TRootState>) => | ||
createNestedCursor<TRootState, TNestedState>(cursor, key); | ||
} | ||
export function isCursorFunction<TState extends IState>(cursor: CursorType<TState>): cursor is () => ICursor<TState> { | ||
return typeof cursor == "function"; | ||
export function isCursorFunction<TState>( | ||
cursor: CursorType<TState> | ||
): cursor is () => ICursor<TState> { | ||
return typeof cursor == 'function'; | ||
} | ||
@@ -39,6 +51,10 @@ | ||
export const rootCursor: ICursor<IState> = { | ||
key: rootStateKey | ||
key: rootStateKey, | ||
}; | ||
export const bootstrap = (defaultState: IState | null, withStateFreezing: boolean | (() => boolean) = false, subStateSeparator: string = '.') => { | ||
export const bootstrap = ( | ||
defaultState: IState | null, | ||
withStateFreezing: boolean | (() => boolean) = false, | ||
subStateSeparator: string = '.' | ||
) => { | ||
stateSeparator = subStateSeparator; | ||
@@ -49,5 +65,10 @@ state = defaultState; | ||
export const isExistingCursor = <TState>(cursor: CursorType<TState>): boolean => { | ||
export const isExistingCursor = <TState>( | ||
cursor: CursorType<TState> | ||
): boolean => { | ||
cursor = isCursorFunction(cursor) ? cursor() : cursor; | ||
const hasExistingInnerStateInArray = (innerState: IState[], path: string[]): boolean => { | ||
const hasExistingInnerStateInArray = ( | ||
innerState: IState[], | ||
path: string[] | ||
): boolean => { | ||
const index = Number(path.shift()); | ||
@@ -57,11 +78,11 @@ return index < innerState.length | ||
: false; | ||
} | ||
const hasExistingInnerState = (innerState: IState, path: string[]): boolean => { | ||
if (path.length === 0) | ||
return true; | ||
}; | ||
const hasExistingInnerState = ( | ||
innerState: IState, | ||
path: string[] | ||
): boolean => { | ||
if (path.length === 0) return true; | ||
const subPath = path.shift(); | ||
if (!subPath) | ||
return true; | ||
if ((<any>innerState)[subPath] === undefined) | ||
return false; | ||
if (!subPath) return true; | ||
if ((<any>innerState)[subPath] === undefined) return false; | ||
const prop = (<any>innerState)[subPath]; | ||
@@ -79,3 +100,3 @@ return Array.isArray(prop) | ||
: hasExistingInnerState(state, cursor.key.split(stateSeparator)); | ||
} | ||
}; | ||
@@ -85,7 +106,5 @@ export const getState = <TState>(cursor: CursorType<TState>): TState => { | ||
const getInnerState = (innerState: IState, path: string[]): IState => { | ||
if (path.length === 0) | ||
return innerState; | ||
if (path.length === 0) return innerState; | ||
const subPath = path.shift(); | ||
if (!subPath) | ||
return innerState; | ||
if (!subPath) return innerState; | ||
checkSubstate(innerState, subPath, path, cursorValue); | ||
@@ -101,20 +120,25 @@ const prop = (<any>innerState)[subPath]; | ||
return <TState>(cursorValue.key === rootStateKey | ||
? state | ||
: getInnerState(state, cursorValue.key.split(stateSeparator))); | ||
return <TState>( | ||
(cursorValue.key === rootStateKey | ||
? state | ||
: getInnerState(state, cursorValue.key.split(stateSeparator))) | ||
); | ||
}; | ||
export const setState = <TState>(cursor: CursorType<TState>, updatedState: TState, canCreateObjectsOnPath = false) => { | ||
export const setState = <TState>( | ||
cursor: CursorType<TState>, | ||
updatedState: TState, | ||
canCreateObjectsOnPath = false | ||
) => { | ||
const cursorValue = isCursorFunction(cursor) ? cursor() : cursor; | ||
const setInnerState = <TInnerState>(innerState: TInnerState, path: string[]): TInnerState => { | ||
if (path.length === 0) | ||
return <any>updatedState; | ||
const setInnerState = <TInnerState>( | ||
innerState: TInnerState, | ||
path: string[] | ||
): TInnerState => { | ||
if (path.length === 0) return <any>updatedState; | ||
const subPath = path.shift(); | ||
if (!subPath) | ||
return <any>updatedState; | ||
if (!subPath) return <any>updatedState; | ||
if (canCreateObjectsOnPath) | ||
createSubstate(innerState, subPath); | ||
else | ||
checkSubstate(innerState, subPath, path, cursorValue); | ||
if (canCreateObjectsOnPath) createSubstate(innerState, subPath); | ||
else checkSubstate(innerState, subPath, path, cursorValue); | ||
const prop = (<any>innerState)[subPath]; | ||
@@ -125,9 +149,9 @@ let newSubState: Object | Array<IState> | null = null; | ||
newSubState = [...prop]; | ||
(<any>newSubState)[index] = setInnerState((<any>newSubState)[index], path); | ||
} | ||
else | ||
newSubState = setInnerState(prop, path); | ||
(<any>newSubState)[index] = setInnerState( | ||
(<any>newSubState)[index], | ||
path | ||
); | ||
} else newSubState = setInnerState(prop, path); | ||
if (newSubState === prop) | ||
return innerState; | ||
if (newSubState === prop) return innerState; | ||
@@ -142,14 +166,21 @@ const newState = h.shallowCopy(innerState); | ||
state = | ||
cursorValue.key === rootStateKey | ||
? updatedState | ||
: setInnerState(state, cursorValue.key.split(stateSeparator)); | ||
if (h.isFunction(freezing) ? freezing() : freezing) | ||
h.deepFreeze(state); | ||
state = isRootCursor(updatedState, cursorValue) | ||
? updatedState | ||
: setInnerState(state, cursorValue.key.split(stateSeparator)); | ||
if (h.isFunction(freezing) ? freezing() : freezing) h.deepFreeze(state); | ||
d.log('Current state:', state); | ||
}; | ||
function checkSubstate<TCurrentState, TTargetState>(s: TCurrentState, subPath: string, remainingPath: string[], cursor: ICursor<TTargetState>) { | ||
if (remainingPath.length === 0 && cursor.isUndefinable) | ||
return; | ||
function isRootCursor(_: unknown, cursorValue: ICursor<unknown>): _ is IState { | ||
return cursorValue.key === rootStateKey; | ||
} | ||
function checkSubstate<TCurrentState, TTargetState>( | ||
s: TCurrentState, | ||
subPath: string, | ||
remainingPath: string[], | ||
cursor: ICursor<TTargetState> | ||
) { | ||
if (remainingPath.length === 0 && cursor.isUndefinable) return; | ||
if ((<any>s)[subPath] === undefined) | ||
@@ -160,4 +191,3 @@ throw new Error(`State for cursor key (${cursor.key}) does not exist.`); | ||
function createSubstate<TState>(s: TState, subPath: string) { | ||
if ((<any>s)[subPath] === undefined) | ||
(<any>s)[subPath] = {}; | ||
if ((<any>s)[subPath] === undefined) (<any>s)[subPath] = {}; | ||
} | ||
@@ -167,3 +197,5 @@ | ||
if (state === null) | ||
throw new Error('Default state must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).'); | ||
throw new Error( | ||
'Default state must be set before first usage through bootstrap(defaultState, () => { yourRenderCallback(); }).' | ||
); | ||
return true; | ||
@@ -173,5 +205,4 @@ } | ||
function isValidCursorKey<TState>(cursor: ICursor<TState>): boolean { | ||
if (cursor.key === null) | ||
throw new Error( 'Cursor key cannot be null.'); | ||
if (cursor.key === null) throw new Error('Cursor key cannot be null.'); | ||
return true; | ||
} | ||
} |
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
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
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
207134
17
1243
1