UNPKG

overmind

Version:
648 lines (646 loc) • 25.5 kB
const require_rolldown_runtime = require('./_virtual/rolldown_runtime.js'); const require_internalTypes = require('./internalTypes.js'); const require_derived = require('./derived.js'); const require_rehydrate = require('./rehydrate.js'); const require_Devtools = require('./Devtools.js'); const require_proxyfyEffects = require('./proxyfyEffects.js'); const require_utils = require('./utils.js'); let proxy_state_tree = require("proxy-state-tree"); proxy_state_tree = require_rolldown_runtime.__toESM(proxy_state_tree); let betsy = require("betsy"); let is_plain_obj = require("is-plain-obj"); is_plain_obj = require_rolldown_runtime.__toESM(is_plain_obj); //#region src/Overmind.ts const hotReloadingCache = {}; var Overmind = class { constructor(configuration, options = {}, mode = { mode: require_utils.MODE_DEFAULT }) { this.actionReferences = {}; this.nextExecutionId = 0; this.rehydrateMutationsForHotReloading = []; this.isStrict = false; this.currentExecution = null; this.reaction = (stateCallback, updateCallback, options$1 = {}) => { let disposer; if (options$1.nested) { const value = stateCallback(this.state); if (!value || !value[proxy_state_tree.IS_PROXY]) throw new Error("You have to return an object or array from the Overmind state when using a \"nested\" reaction"); const path = value[proxy_state_tree.PATH]; disposer = this.addFlushListener((mutations) => { mutations.forEach((mutation) => { if (mutation.path.startsWith(path)) updateCallback(path ? path.split(this.delimiter).reduce((aggr, key) => aggr[key], this.state) : this.state); }); }); } else { const tree = this.proxyStateTreeInstance.getTrackStateTree(); let returnValue; const updateReaction = () => { disposer?.(); tree.trackScope(() => returnValue = stateCallback(tree.state)); disposer = tree.subscribe(() => { updateReaction(); updateCallback(returnValue); }); }; updateReaction(); } if (options$1.immediate) updateCallback(stateCallback(this.state)); return () => disposer?.(); }; this.addMutationListener = (cb) => { return this.proxyStateTreeInstance.onMutation(cb); }; this.addFlushListener = (cb) => { return this.proxyStateTreeInstance.onFlush(cb); }; const name = options.name || "OvermindApp"; const devEnv = options.devEnv || "development"; const isNode = typeof process !== "undefined" && process.title && process.title.includes("node"); this.delimiter = options.delimiter || "."; this.isStrict = Boolean(options.strict); if (require_utils.ENVIRONMENT === devEnv && mode.mode === require_utils.MODE_DEFAULT && options.hotReloading !== false && !isNode) if (hotReloadingCache[name]) return hotReloadingCache[name].reconfigure(configuration); else hotReloadingCache[name] = this; const eventHub = mode.mode === require_utils.MODE_SSR ? new require_utils.MockedEventEmitter() : new betsy.EventEmitter(); const proxyStateTreeInstance = this.createProxyStateTree(configuration, eventHub, mode.mode === require_utils.MODE_TEST || require_utils.ENVIRONMENT === devEnv, mode.mode === require_utils.MODE_SSR); this.originalConfiguration = configuration; this.state = proxyStateTreeInstance.state; this.effects = configuration.effects || {}; this.proxyStateTreeInstance = proxyStateTreeInstance; this.eventHub = eventHub; this.mode = mode; this.actions = this.getActions(configuration.actions); if (mode.mode === require_utils.MODE_SSR) return; if (require_utils.ENVIRONMENT === devEnv && mode.mode === require_utils.MODE_DEFAULT && typeof window !== "undefined") { let warning = "OVERMIND: You are running in DEVELOPMENT mode."; if (options.logProxies !== true) { const originalConsoleLog = console.log; console.log = (...args) => originalConsoleLog.apply(console, args.map((arg) => arg && arg[proxy_state_tree.IS_PROXY] ? arg[proxy_state_tree.VALUE] : arg)); warning += "\n\n - To improve debugging experience \"console.log\" will NOT log proxies from Overmind, but the actual value. Please see docs to turn off this behaviour"; } if (options.devtools || typeof location !== "undefined" && location.hostname === "localhost" && options.devtools !== false) { const host = options.devtools === true ? "localhost:3031" : options.devtools; const name$1 = options.name ? options.name : typeof document === "undefined" ? "NoName" : document.title || "NoName"; this.initializeDevtools(host, name$1, eventHub, configuration.actions, options.devtoolsLogLevel); } else if (options.devtools !== false) warning += "\n\n - You are not running on localhost. You will have to manually define the devtools option to connect"; if (!require_utils.IS_TEST) console.warn(warning); } if (require_utils.ENVIRONMENT === "production" && mode.mode === require_utils.MODE_DEFAULT) { eventHub.on(require_internalTypes.EventType.OPERATOR_ASYNC, (execution) => { if (!execution.parentExecution || !execution.parentExecution.isRunning) proxyStateTreeInstance.getMutationTree().flush(true); }); eventHub.on(require_internalTypes.EventType.ACTION_END, (execution) => { if (!execution.parentExecution || !execution.parentExecution.isRunning) proxyStateTreeInstance.getMutationTree().flush(); }); let nextTick; const flushTree = () => { proxyStateTreeInstance.getMutationTree().flush(true); }; this.proxyStateTreeInstance.onMutation(() => { nextTick && clearTimeout(nextTick); nextTick = setTimeout(flushTree, 0); }); } else if (mode.mode === require_utils.MODE_DEFAULT || mode.mode === require_utils.MODE_TEST) { if (require_utils.ENVIRONMENT === "test" || this.devtools && options.hotReloading !== false) eventHub.on(require_internalTypes.EventType.MUTATIONS, (execution) => { this.rehydrateMutationsForHotReloading = this.rehydrateMutationsForHotReloading.concat(execution.mutations); }); eventHub.on(require_internalTypes.EventType.OPERATOR_ASYNC, (execution) => { if (!execution.parentExecution || !execution.parentExecution.isRunning) { const flushData = execution.flush(true); if (this.devtools && flushData.mutations.length) this.devtools.send({ type: "flush", data: { ...execution, ...flushData } }); } }); eventHub.on(require_internalTypes.EventType.ACTION_END, (execution) => { if (!execution.parentExecution || !execution.parentExecution.isRunning) { const flushData = execution.flush(); if (this.devtools && flushData.mutations.length) this.devtools.send({ type: "flush", data: { ...execution, ...flushData } }); } }); } if (mode.mode === require_utils.MODE_DEFAULT) { const onInitialize = this.createAction("onInitialize", require_utils.createOnInitialize()); this.initialized = Promise.resolve(onInitialize(this)); } else this.initialized = Promise.resolve(null); } getCurrentExecution() { return this.currentExecution; } createProxyStateTree(configuration, eventHub, devmode, ssr) { const proxyStateTreeInstance = new proxy_state_tree.ProxyStateTree(this.getState(configuration), { devmode: devmode && !ssr, ssr, delimiter: this.delimiter, transformPath: (path) => { const execution = this.getCurrentExecution(); if (!execution?.namespacePath?.length) return path; if (!path) return path; const firstSegment = path.split(this.delimiter)[0]; if ((this.state && typeof this.state === "object" ? Object.keys(this.state) : []).includes(firstSegment)) return path; return execution.namespacePath.join(this.delimiter) + this.delimiter + path; }, onSetFunction: (tree, path, target, prop, func) => { if (func[require_derived.IS_DERIVED_CONSTRUCTOR]) return new require_derived.Derived(func); return func; }, onGetFunction: (tree, path, target, prop) => { const func = target[prop]; if (func[require_derived.IS_DERIVED]) return func(eventHub, tree, proxyStateTreeInstance, path.split(this.delimiter)); if (func[require_derived.IS_DERIVED_CONSTRUCTOR]) { const derived = new require_derived.Derived(func); target[prop] = derived; return derived(eventHub, tree, proxyStateTreeInstance, path.split(this.delimiter)); } return func; }, onGetter: devmode ? (path, value) => { this.eventHub.emitAsync(require_internalTypes.EventType.GETTER, { path, value }); } : void 0, getDevtools: () => { if (devmode && !ssr && this.devtools) return this.devtools; } }); return proxyStateTreeInstance; } createExecution(name, action, parentExecution) { const namespacePath = name.split("."); namespacePath.pop(); if (require_utils.ENVIRONMENT === "production") return { [require_utils.EXECUTION]: true, parentExecution, namespacePath, actionName: name, getMutationTree: () => { return this.proxyStateTreeInstance.getMutationTree(); }, getTrackStateTree: () => { return this.proxyStateTreeInstance.getTrackStateTree(); }, emit: this.eventHub.emit.bind(this.eventHub) }; const mutationTrees = []; return { [require_utils.EXECUTION]: true, namespacePath, actionId: name, executionId: this.nextExecutionId++, actionName: name, operatorId: 0, isRunning: true, parentExecution, path: [], emit: this.eventHub.emit.bind(this.eventHub), send: this.devtools ? this.devtools.send.bind(this.devtools) : () => {}, trackEffects: this.trackEffects.bind(this, this.effects), getNextOperatorId: (() => { let currentOperatorId = 0; return () => ++currentOperatorId; })(), flush: parentExecution ? parentExecution.flush : (isAsync) => { return this.proxyStateTreeInstance.flush(mutationTrees, isAsync); }, getMutationTree: parentExecution ? parentExecution.getMutationTree : () => { const mutationTree = this.proxyStateTreeInstance.getMutationTree(); mutationTrees.push(mutationTree); return mutationTree; }, getTrackStateTree: () => { return this.proxyStateTreeInstance.getTrackStateTree(); }, onFlush: (cb) => { return this.proxyStateTreeInstance.onFlush(cb); }, scopeValue: (value, tree) => { return this.scopeValue(value, tree); } }; } createScopedProxy(target, namespacePath) { if (!namespacePath.length) return target; return new Proxy(target, { get: (obj, prop) => { if (typeof prop === "symbol" || String(prop).startsWith("__")) return obj[prop]; const namespaceObj = namespacePath.reduce((aggr, key) => aggr?.[key], obj); if (require_utils.isStateMachine(namespaceObj)) { if (prop in namespaceObj) { const value$1 = namespaceObj[prop]; if (prop in obj && require_utils.isStateMachine(obj[prop])) return obj[prop]; if (typeof value$1 === "function") return value$1.bind(namespaceObj); return value$1; } return obj[prop]; } const value = namespaceObj && prop in namespaceObj ? namespaceObj[prop] : obj[prop]; if (namespaceObj && prop in namespaceObj && typeof value === "function") return value.bind(namespaceObj); return value; }, has: (obj, prop) => { const namespaceObj = namespacePath.reduce((aggr, key) => aggr?.[key], obj); return namespaceObj && prop in namespaceObj || prop in obj; }, ownKeys: (obj) => { const namespaceObj = namespacePath.reduce((aggr, key) => aggr?.[key], obj); if (require_utils.isStateMachine(namespaceObj)) return Object.keys(namespaceObj); const namespaceKeys = namespaceObj ? Object.keys(namespaceObj) : []; const rootKeys = Object.keys(obj); return Array.from(new Set([...namespaceKeys, ...rootKeys])); }, getOwnPropertyDescriptor: (obj, prop) => { const namespaceObj = namespacePath.reduce((aggr, key) => aggr?.[key], obj); if (namespaceObj && prop in namespaceObj) return Object.getOwnPropertyDescriptor(namespaceObj, prop) || { configurable: true, enumerable: true }; return Object.getOwnPropertyDescriptor(obj, prop) || { configurable: true, enumerable: true }; } }); } createContext(execution, tree) { const namespacePath = execution.namespacePath || []; const actionsProxy = require_utils.createActionsProxy(this.actions, (action) => { return (value) => action(value, execution.isRunning ? execution : null); }); return { state: tree.state, actions: namespacePath.length ? this.createScopedProxy(actionsProxy, namespacePath) : actionsProxy, execution, proxyStateTree: this.proxyStateTreeInstance, effects: namespacePath.length ? this.createScopedProxy(this.trackEffects(this.effects, execution), namespacePath) : this.trackEffects(this.effects, execution), addNamespace: this.addNamespace.bind(this), reaction: this.reaction.bind(this), addMutationListener: this.addMutationListener.bind(this), addFlushListener: this.addFlushListener.bind(this) }; } addNamespace(configuration, path, existingState) { const state = existingState || this.state; const namespaceKey = path.pop(); if (configuration.state) { const stateTarget = path.reduce((aggr, key) => aggr[key], state); stateTarget[namespaceKey] = require_utils.processState(configuration.state); } if (configuration.actions) { const actionsTarget = path.reduce((aggr, key) => aggr[key], this.actions); actionsTarget[namespaceKey] = this.getActions(configuration.actions); } if (configuration.effects) { const effectsTarget = path.reduce((aggr, key) => aggr[key], this.effects); effectsTarget[namespaceKey] = configuration.effects; } } scopeValue(value, tree) { if (!value) return value; if (value[proxy_state_tree.IS_PROXY]) return this.proxyStateTreeInstance.rescope(value, tree); else if ((0, is_plain_obj.default)(value)) return Object.assign({}, ...Object.keys(value).map((key) => ({ [key]: this.proxyStateTreeInstance.rescope(value[key], tree) }))); else return value; } addExecutionMutation(mutation) { this.mutations.push(mutation); } createAction(name, originalAction) { this.actionReferences[name] = originalAction; const actionFunc = (value, boundExecution) => { const action = this.actionReferences[name]; boundExecution = boundExecution && boundExecution[require_utils.EXECUTION] ? boundExecution : void 0; if (require_utils.ENVIRONMENT === "production" || action[require_utils.IS_OPERATOR] || this.mode.mode === require_utils.MODE_SSR) { const execution = this.createExecution(name, action, boundExecution); const previousExecution = this.currentExecution; this.currentExecution = execution; try { this.eventHub.emit(require_internalTypes.EventType.ACTION_START, { ...execution, value }); if (action[require_utils.IS_OPERATOR]) return new Promise((resolve, reject) => { action(null, { ...this.createContext(execution, this.proxyStateTreeInstance), value }, (err, finalContext) => { execution.isRunning = false; finalContext && this.eventHub.emit(require_internalTypes.EventType.ACTION_END, { ...finalContext.execution, operatorId: finalContext.execution.operatorId - 1 }); if (err) reject(err); else resolve(finalContext.value); }); }).finally(() => { this.currentExecution = previousExecution; }); else { const mutationTree = execution.getMutationTree(); if (this.isStrict) mutationTree.blockMutations(); const returnValue = action(this.createContext(execution, mutationTree), value); this.eventHub.emit(require_internalTypes.EventType.ACTION_END, execution); return returnValue; } } finally { if (!action[require_utils.IS_OPERATOR]) this.currentExecution = previousExecution; } } else { const execution = { ...this.createExecution(name, action, boundExecution), operatorId: 0, type: "action" }; const previousExecution = this.currentExecution; this.currentExecution = execution; this.eventHub.emit(require_internalTypes.EventType.ACTION_START, { ...execution, value }); this.eventHub.emit(require_internalTypes.EventType.OPERATOR_START, execution); const mutationTree = execution.getMutationTree(); if (this.isStrict) mutationTree.blockMutations(); mutationTree.onMutation((mutation) => { this.eventHub.emit(require_internalTypes.EventType.MUTATIONS, { ...execution, mutations: [mutation] }); }); const scopedValue = this.scopeValue(value, mutationTree); const context = this.createContext(execution, mutationTree); let result; let isAsync = false; try { let pendingFlush; mutationTree.onMutation((mutation) => { if (pendingFlush) clearTimeout(pendingFlush); if (this.mode.mode === require_utils.MODE_TEST) this.addExecutionMutation(mutation); else if (this.mode.mode === require_utils.MODE_DEFAULT) pendingFlush = setTimeout(() => { pendingFlush = null; const flushData = execution.flush(true); if (this.devtools && flushData.mutations.length) this.devtools.send({ type: "flush", data: { ...execution, ...flushData, mutations: flushData.mutations } }); }); }); result = action(context, scopedValue); isAsync = require_utils.isPromise(result); if (isAsync) { this.eventHub.emit(require_internalTypes.EventType.OPERATOR_ASYNC, execution); result = result.then((promiseResult) => { execution.isRunning = false; if (!boundExecution) mutationTree.dispose(); this.eventHub.emit(require_internalTypes.EventType.OPERATOR_END, { ...execution, isAsync: true, result: void 0 }); this.eventHub.emit(require_internalTypes.EventType.ACTION_END, execution); return promiseResult; }).catch((error) => { execution.isRunning = false; if (!boundExecution) mutationTree.dispose(); this.eventHub.emit(require_internalTypes.EventType.OPERATOR_END, { ...execution, isAsync: true, result: void 0, error: error.message }); this.eventHub.emit(require_internalTypes.EventType.ACTION_END, execution); throw error; }).finally(() => { this.currentExecution = previousExecution; }); } else { execution.isRunning = false; if (!boundExecution) mutationTree.dispose(); this.eventHub.emit(require_internalTypes.EventType.OPERATOR_END, { ...execution, isAsync: false, result: void 0 }); this.eventHub.emit(require_internalTypes.EventType.ACTION_END, execution); } return result; } catch (err) { this.eventHub.emit(require_internalTypes.EventType.OPERATOR_END, { ...execution, isAsync: false, result: void 0, error: err.message }); this.eventHub.emit(require_internalTypes.EventType.ACTION_END, execution); throw err; } finally { if (!isAsync) this.currentExecution = previousExecution; } } }; return actionFunc; } trackEffects(effects = {}, execution) { if (require_utils.ENVIRONMENT === "production") return effects; return require_proxyfyEffects.proxifyEffects(this.effects, (effect) => { let result; try { if (this.mode.mode === require_utils.MODE_TEST) result = this.mode.options.effectsCallback(effect); else { this.eventHub.emit(require_internalTypes.EventType.EFFECT, { ...execution, ...effect, args: effect.args, isPending: true, error: false }); result = effect.func.apply(this, effect.args); } } catch (error) { this.eventHub.emit(require_internalTypes.EventType.EFFECT, { ...execution, ...effect, args: effect.args, isPending: false, error: error.message }); throw error; } if (require_utils.isPromise(result)) { this.eventHub.emit(require_internalTypes.EventType.EFFECT, { ...execution, ...effect, args: effect.args, isPending: true, error: false }); return result.then((promisedResult) => { this.eventHub.emit(require_internalTypes.EventType.EFFECT, { ...execution, ...effect, args: effect.args, result: promisedResult, isPending: false, error: false }); return promisedResult; }).catch((error) => { this.eventHub.emit(require_internalTypes.EventType.EFFECT, { ...execution, ...effect, args: effect.args, isPending: false, error: error && error.message }); throw error; }); } this.eventHub.emit(require_internalTypes.EventType.EFFECT, { ...execution, ...effect, args: effect.args, result, isPending: false, error: false }); return result; }); } initializeDevtools(host, name, eventHub, actions, logLevel = "error") { if (require_utils.ENVIRONMENT === "production") return; const devtools = new require_Devtools.Devtools(name, logLevel); devtools.connect(host, (message) => { switch (message.type) { case "refresh": location.reload(); break; case "executeAction": { const action = message.data.name.split(".").reduce((aggr, key) => aggr[key], this.actions); message.data.payload ? action(JSON.parse(message.data.payload)) : action(); break; } case "mutation": { const tree = this.proxyStateTreeInstance.getMutationTree(); const path = message.data.path.slice(); const value = JSON.parse(`{ "value": ${message.data.value} }`).value; const key = path.pop(); const state = path.reduce((aggr, key$1) => aggr[key$1], tree.state); state[key] = value; tree.flush(true); tree.dispose(); this.devtools.send({ type: "state", data: { path: message.data.path, value } }); break; } } }); for (const type in require_internalTypes.EventType) eventHub.on(require_internalTypes.EventType[type], ((eventType) => (data) => { devtools.send({ type: require_internalTypes.EventType[type], data }); if (eventType === require_internalTypes.EventType.MUTATIONS) data.mutations.forEach((mutation) => { const value = mutation.path.split(this.delimiter).reduce((aggr, key) => aggr[key], this.proxyStateTreeInstance.state); if ((0, is_plain_obj.default)(value)) Object.keys(value).forEach((key) => value[key]); else if (Array.isArray(value)) value.forEach((item) => { if ((0, is_plain_obj.default)(item)) Object.keys(item).forEach((key) => item[key]); }); }); if (eventType === require_internalTypes.EventType.DERIVED_DIRTY) data.derivedPath.reduce((aggr, key) => aggr[key], this.proxyStateTreeInstance.state); })(require_internalTypes.EventType[type])); devtools.send({ type: "init", data: { state: this.proxyStateTreeInstance.state, actions: require_utils.getActionPaths(actions), delimiter: this.delimiter, features: { transitions: true, charts: require_utils.detectStateCharts(this.state) } } }); this.devtools = devtools; } getState(configuration) { let state = {}; if (configuration.state) state = require_utils.processState(configuration.state); return state; } getActions(actions = {}, path = []) { return Object.keys(actions).reduce((aggr, name) => { if (typeof actions[name] === "function") { const action = this.createAction(path.concat(name).join("."), actions[name]); action.displayName = path.concat(name).join("."); return Object.assign(aggr, { [name]: action }); } return Object.assign(aggr, { [name]: this.getActions(actions[name], path.concat(name)) }); }, {}); } updateActions(actions = {}, path = []) { Object.keys(actions).forEach((name) => { if (typeof actions[name] === "function") { const actionName = path.concat(name).join("."); if (this.actionReferences[actionName]) this.actionReferences[actionName] = actions[name]; else { const target = path.reduce((aggr, key) => { if (!aggr[key]) aggr[key] = {}; return aggr[key]; }, this.actions); target[name] = this.createAction(actionName, actions[name]); target[name].displayName = path.concat(name).join("."); } } else this.updateActions(actions[name], path.concat(name)); }, {}); } getTrackStateTree() { return this.proxyStateTreeInstance.getTrackStateTree(); } getMutationTree() { return this.proxyStateTreeInstance.getMutationTree(); } reconfigure(configuration) { const changeMutations = require_utils.getChangeMutations(this.originalConfiguration.state, configuration.state || {}); this.updateActions(configuration.actions); this.effects = configuration.effects || {}; const mutationTree = this.proxyStateTreeInstance.getMutationTree(); require_rehydrate.rehydrate(mutationTree.state, changeMutations); this.rehydrateMutationsForHotReloading.forEach((mutation) => { try { require_rehydrate.rehydrate(mutationTree.state, [mutation]); } catch (error) {} }); mutationTree.flush(); mutationTree.dispose(); if (this.devtools) this.devtools.send({ type: "re_init", data: { state: this.state, actions: require_utils.getActionPaths(configuration.actions), features: { transitions: true, charts: require_utils.detectStateCharts(this.state) } } }); return this; } }; //#endregion exports.Overmind = Overmind; //# sourceMappingURL=Overmind.js.map