cli-legend
Version:
"A fun command-line rogurelike dungeon game"
119 lines (118 loc) • 3.71 kB
JavaScript
/* eslint @typescript-eslint/no-explicit-any: ["off"] */
Object.defineProperty(exports, "__esModule", { value: true });
exports.effectScheduler = void 0;
exports.withHooks = withHooks;
exports.readline = readline;
exports.withUpdates = withUpdates;
exports.withPointer = withPointer;
exports.handleChange = handleChange;
const node_async_hooks_1 = require("node:async_hooks");
const errors_js_1 = require("./errors.js");
const hookStorage = new node_async_hooks_1.AsyncLocalStorage();
function createStore(rl) {
const store = {
rl,
hooks: [],
hooksCleanup: [],
hooksEffect: [],
index: 0,
handleChange() { },
};
return store;
}
// Run callback in with the hook engine setup.
function withHooks(rl, cb) {
const store = createStore(rl);
return hookStorage.run(store, () => {
function cycle(render) {
store.handleChange = () => {
store.index = 0;
render();
};
store.handleChange();
}
return cb(cycle);
});
}
// Safe getStore utility that'll return the store or throw if undefined.
function getStore() {
const store = hookStorage.getStore();
if (!store) {
throw new errors_js_1.HookError('[Inquirer] Hook functions can only be called from within a prompt');
}
return store;
}
function readline() {
return getStore().rl;
}
// Merge state updates happening within the callback function to avoid multiple renders.
function withUpdates(fn) {
const wrapped = (...args) => {
const store = getStore();
let shouldUpdate = false;
const oldHandleChange = store.handleChange;
store.handleChange = () => {
shouldUpdate = true;
};
const returnValue = fn(...args);
if (shouldUpdate) {
oldHandleChange();
}
store.handleChange = oldHandleChange;
return returnValue;
};
return node_async_hooks_1.AsyncResource.bind(wrapped);
}
function withPointer(cb) {
const store = getStore();
const { index } = store;
const pointer = {
get() {
return store.hooks[index];
},
set(value) {
store.hooks[index] = value;
},
initialized: index in store.hooks,
};
const returnValue = cb(pointer);
store.index++;
return returnValue;
}
function handleChange() {
getStore().handleChange();
}
exports.effectScheduler = {
queue(cb) {
const store = getStore();
const { index } = store;
store.hooksEffect.push(() => {
store.hooksCleanup[index]?.();
const cleanFn = cb(readline());
if (cleanFn != null && typeof cleanFn !== 'function') {
throw new errors_js_1.ValidationError('useEffect return value must be a cleanup function or nothing.');
}
store.hooksCleanup[index] = cleanFn;
});
},
run() {
const store = getStore();
withUpdates(() => {
store.hooksEffect.forEach((effect) => {
effect();
});
// Warning: Clean the hooks before exiting the `withUpdates` block.
// Failure to do so means an updates would hit the same effects again.
store.hooksEffect.length = 0;
})();
},
clearAll() {
const store = getStore();
store.hooksCleanup.forEach((cleanFn) => {
cleanFn?.();
});
store.hooksEffect.length = 0;
store.hooksCleanup.length = 0;
},
};
;