UNPKG

2.58 kBJavaScriptView Raw
1function systemReducer(state, action) {
2 switch (action.type) {
3 case "SET": {
4 const { name, value } = action.payload
5 return {
6 ...state,
7 [name]: value,
8 }
9 }
10 case "MERGE": {
11 return {
12 ...state,
13 ...action.payload,
14 }
15 }
16 }
17}
18
19/*
20
21Notes
22
23 - any call to dispatch will immediately invoke the reducer
24 - any call to dispatch within middleware is also immediately processed
25 - SET and MERGE are not interceptable by middleware
26 - middleware functions are assumed to call next synchronously
27 - the render function (subscriber) is invoked once for every call to dispatch
28 - the render function is debounced
29
30*/
31
32export function configure(
33 { update = {}, middleware = [], derivations = {}, initialState = {} },
34 node
35) {
36 let subscribers = []
37 let state
38 let updatedCallback = () => {}
39
40 function updateState(o) {
41 state = { ...o }
42 for (let k in derivations) {
43 state[k] = derivations[k](o)
44 }
45 }
46
47 updateState(initialState)
48
49 function getState() {
50 return { ...state }
51 }
52
53 function subscribe(fn) {
54 subscribers.push(fn)
55 }
56
57 function flush() {
58 subscribers.forEach((fn) => fn())
59 subscribers = []
60 }
61
62 function onUpdate(fn) {
63 updatedCallback = fn
64 }
65
66 function dispatch(action) {
67 const { type } = action
68 if (type === "SET" || type === "MERGE") {
69 updateState(systemReducer(getState(), action))
70 } else {
71 if (middleware.length) {
72 let mw = middleware.slice()
73
74 let next = (action) => {
75 if (mw.length) {
76 let x = mw.shift()
77
78 if (action.type in x) {
79 x[action.type](action, next, {
80 getState,
81 dispatch,
82 afterNextRender: subscribe,
83 })
84 } else {
85 next(action)
86 }
87 } else if (action.type in update) {
88 updateState(update[action.type](getState(), action))
89 }
90 }
91
92 let x = mw.shift()
93
94 if (type in x) {
95 x[type](action, next, {
96 getState,
97 dispatch,
98 afterNextRender: subscribe,
99 })
100 } else {
101 next(action)
102 }
103 } else if (type in update) {
104 updateState(update[type](getState(), action))
105 }
106 }
107 updatedCallback()
108 }
109
110 for (let actionName in update) {
111 if (actionName.startsWith("$")) {
112 node.addEventListener(actionName, ({ detail }) => dispatch(detail))
113 }
114 }
115
116 return {
117 dispatch,
118 getState,
119 onUpdate,
120 flush,
121 }
122}