Skip to content

Instantly share code, notes, and snippets.

@HyphnKnight
Last active November 12, 2016 19:54
Show Gist options
  • Select an option

  • Save HyphnKnight/ec8cdc50996c0e68fcb41ea2ce382cf0 to your computer and use it in GitHub Desktop.

Select an option

Save HyphnKnight/ec8cdc50996c0e68fcb41ea2ce382cf0 to your computer and use it in GitHub Desktop.
A very small state management tool for javascript.
/* Store.js */
function identity( x ) {
return x;
}
function curry2( func, x, y ) {
return function curryInside( z ) {
return func( x, y, z );
}
}
function uniqueId() {
return Math.floor( Math.random() * 100000 ) + '-' + Date.now();
}
function isDate( date ) {
return Object.prototype.toString.call( date ) === '[object Date]';
}
function isComparable( value ) {
return typeof value === 'number' ||
typeof value === 'boolean' ||
typeof value === 'string' ||
typeof value === 'symbol' ||
typeof value === 'function';
}
function isEqual( valueA, valueB ) {
if ( typeof valueA !== typeof valueB ) {
return false;
} else if ( typeof valueA === 'undefined' ) {
return true;
} else if ( valueA === null || valueB === null || isComparable( valueA ) ) {
return valueA === valueB;
} else if ( isDate( valueA ) && isDate( valueB ) ) {
return valueA.getTime() === valueB.getTime();
} else if ( Array.isArray( valueA ) && Array.isArray( valueB ) ) {
return valueA.length === valueB.length && !valueA.find( ( val, index ) => !isEqual( valueB[ index ], val ) );
} else if ( valueA instanceof Map && valueB instanceof Map ) {
return valueA.size === valueB.size && isEqual( Array.from( valueA.entries() ), Array.from( valueB.entries() ) );
} else if ( valueA instanceof Set && valueB instanceof Set ) {
return valueA.size === valueB.size && isEqual( Array.from( valueA ), Array.from( valueB ) );
} else if ( !!valueA.__store__ && !!valueB.__store__ ) {
return isEqual( valueA.__store__, valueB.__store__ );
} else if ( Object.keys( valueA ).length === Object.keys( valueA ).length ) {
return !Object.keys( valueA ).find( val => !isEqual( valueA[ val ], valueB[ val ] ) );
} else {
return false;
}
}
function setStore( store, subscriptions, obj ) {
const objKeys = Object.keys( obj );
let oldStore;
if ( objKeys.map( key => {
if ( !isEqual( obj[key], store.__store__[ key ] ) ) {
if ( !oldStore ) {
oldStore = Object.assign( {}, store.__store__ );
}
store.__store__[ key ] = obj[key];
return true;
} else {
return false;
}
} ).some( identity ) ) {
Object.keys( subscriptions ).forEach( key => subscriptions[ key ]( store, oldStore ) );
}
}
function Store( protoObj ) {
protoObj = typeof protoObj === 'string' ? JSON.parse( protoObj ) : protoObj;
if ( Object.keys( protoObj ).some( key => key === 'subscribe' || key === '__store__' || key === 'set' ) ) {
throw `Store can not be created from an object with reserved keys: subscribe, __store__ and set.`;
}
const
subscriptions = {},
newStore = {
subscribe: func => {
const key = uniqueId();
subscriptions[ key ] = func;
return () => {
delete subscriptions[ key ];
};
},
__store__: {}
};
Object.keys( protoObj ).forEach( key => {
newStore.__store__[ key ] = protoObj[ key ];
Object.defineProperty( newStore, key, {
set: value => {
if ( !isEqual( value, newStore.__store__[ key ] ) ) {
const oldStore = Object.assign( {}, newStore.__store__ );
newStore.__store__[ key ] = value;
Object.keys( subscriptions ).forEach( key => subscriptions[ key ]( newStore, oldStore ) );
}
},
get: () => newStore.__store__[ key ]
} );
} );
Object.defineProperty( newStore, 'JSON', {
set: string => newStore.set( JSON.parse( string ) ),
get: () => JSON.stringify( newStore.__store__ )
} );
newStore[ Symbol.iterator ] = () => ( function iteratorMaker( protoObj ) {
'use strict';
const objKeys = Object.keys( protoObj );
let index = 0;
return {
next: function next() {
const value = protoObj[ objKeys[ index ] ];
let result;
if ( index > objKeys.length - 1 ) {
result = { done: true };
} else {
result = { value, done: false };
++index;
}
return result;
}
}
} )( protoObj );
newStore.set = curry2( setStore, newStore, subscriptions );
return Object.preventExtensions( newStore );
}
export default Store;
/* Example:
const
// Create a Store.
myStore = Store( { myName: 'Rafe', yourName: 'Tara' } ),
// Create a subscription.
cancelSubscribe = myStore.subscribe( state => console.log( state ) );
// This triggers your subscribe callbacks and sets myName to Dave;
myStore.myName = 'Dave';
// This will throw an error because you tried to set a property you did not initialize with.
myStore.theName = 'Jeff';
// The subscribe callback will no longer fire;
cancelSubscribe();
*/
/* Fun Example:
function createEquipment( weapon, armor ) {
return Store( { weapon, armor } );
}
function createBarbarian( name, gender ) {
const
barbInfo = Store({
name,
class: 'Barbarian',
gender: !gender ? 'male' : 'female',
health: 100,
equipment: createEquipment( 'dagger', 'helmet' ),
potions: ['health','mana']
} );
let
possessive = !gender ? 'his' : 'her',
pronoun = !gender ? 'he' : 'she';
barbInfo.equipment.subscribe( state => {
console.log( `${barbInfo.name} has decided to use the ${state.weapon} & ${state.armor} combo!` );
barbInfo.equipment = state;
} );
barbInfo.subscribe( ( state, oldState ) => {
if ( state.gender !== oldState.gender ) {
possessive = !state.gender ? 'his' : 'her';
pronoun = !state.gender ? 'he' : 'she';
}
if ( barbInfo.health <= 0 ) {
console.log( `${barbInfo.name} is dead, quick take ${possessive} ${barbInfo.equipment.armor}!` );
} else if ( barbInfo.health > 0 && barbInfo.health < 90 ) {
console.log( `${barbInfo.name} has just a flesh wound, surely ${pronoun} will maim you with ${possessive} ${barbInfo.equipment.weapon}!` );
} else {
console.log( `${barbInfo.name} is feeling hearty, ${pronoun} is feeling stabby!` );
}
} );
return barbInfo;
}
const
bobTheBarb = createBarbarian( 'Bob' ),
birthaTheBarb = createBarbarian( 'Birtha', true );
function switchEquipment( barb, weapon, armor ) {
barb.equipment.set( { weapon, armor } );
}
function createBite( animal, damage ) {
return function animalBite( barb ) {
console.log( `A ${animal} has bitten ${barb.name}.` );
barb.health -= damage;
}
}
const
spiderBite = createBite( 'spider', 10 ),
wolfBite = createBite( 'wolf', 20 ),
bearBite = createBite( 'bear', 40 ),
dragonBite = createBite( 'dragon', 80 );
setTimeout( () => spiderBite(bobTheBarb), 1000 );
setTimeout( () => wolfBite(bobTheBarb), 2500 );
setTimeout( () => bearBite(birthaTheBarb), 4000 );
setTimeout( () => switchEquipment( bobTheBarb, 'sword','shield'), 5500 );
setTimeout( () => dragonBite(bobTheBarb), 7000 );
setTimeout( () => switchEquipment( birthaTheBarb, 'sword','shield'), 9000 );
setTimeout( () => bearBite(birthaTheBarb), 10500 );
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment