store.js 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. const observable = initial => {
  2. let internal = initial;
  3. const listeners = new Set();
  4. const obs = {};
  5. obs.get = () => internal;
  6. obs.set = newValue => {
  7. const oldValue = internal;
  8. internal = newValue;
  9. listeners.forEach(listener => listener(newValue, oldValue));
  10. };
  11. obs.sub = callback => {
  12. listeners.add(callback);
  13. return () => {
  14. listeners.delete(callback);
  15. };
  16. };
  17. // ADDED FOR MOCKING
  18. obs.reset = () => {
  19. internal = initial;
  20. };
  21. // MOCKING MAKES THIS A DIRECT GET
  22. const useValue = obs.get;
  23. return [obs, useValue];
  24. };
  25. const storeProxy = (store, actions) => {
  26. const read = key => store[key].get();
  27. const write = (key, value) => store[key].set(value);
  28. return new Proxy(
  29. changes =>
  30. Object.entries(changes).forEach(([key, change]) =>
  31. write(key, typeof change === "function" ? change(read(key)) : change)
  32. ),
  33. {
  34. ownKeys: () => [...Reflect.ownKeys(store), ...Reflect.ownKeys(actions)],
  35. get: (_, key) => actions[key] ?? read(key),
  36. set: (_, key, value) => {
  37. if (Object.prototype.hasOwnProperty.call(store, key)) {
  38. write(key, value);
  39. return true;
  40. }
  41. return false;
  42. },
  43. }
  44. );
  45. };
  46. const create = definition => {
  47. const store = Object.create(null);
  48. const hooks = Object.create(null);
  49. const actions = Object.create(null);
  50. const watch = {
  51. onChange: callback =>
  52. Object.entries(store).forEach(([key, obs]) =>
  53. obs.sub((newValue, oldValue) => callback(key, newValue, oldValue))
  54. ),
  55. onCall: callback =>
  56. Object.entries(actions).forEach(([key, act]) => {
  57. actions[key] = (...args) => {
  58. callback(key, ...args);
  59. return act(...args);
  60. };
  61. }),
  62. setGlobal: storeName => {
  63. window[storeName] = storeProxy(store, actions);
  64. },
  65. };
  66. Object.entries(definition(storeProxy(store, actions))).forEach(
  67. ([key, value]) => {
  68. if (typeof value === "function") {
  69. actions[key] = value;
  70. } else {
  71. const hookName = `use${key.charAt(0).toUpperCase()}${key.slice(1)}`;
  72. [store[key], hooks[hookName]] = observable(value);
  73. }
  74. }
  75. );
  76. // ADDED FOR MOCKING
  77. actions.GET_STORED_VALUE = key => store[key].get();
  78. // ADDED FOR MOCKING
  79. actions.SET_STORED_VALUE = (key, value) => store[key].set(value);
  80. // ADDED FOR MOCKING
  81. actions.RESET_STORE = () =>
  82. Object.entries(store).forEach(([, { reset }]) => reset());
  83. return [hooks, actions, watch];
  84. };
  85. export default create;