|
|
|
import { AsyncLocalStorage, AsyncResource } from 'node:async_hooks'; |
|
import { HookError, ValidationError } from './errors.mjs'; |
|
const hookStorage = new AsyncLocalStorage(); |
|
function createStore(rl) { |
|
const store = { |
|
rl, |
|
hooks: [], |
|
hooksCleanup: [], |
|
hooksEffect: [], |
|
index: 0, |
|
handleChange() { }, |
|
}; |
|
return store; |
|
} |
|
|
|
export function withHooks(rl, cb) { |
|
const store = createStore(rl); |
|
return hookStorage.run(store, () => { |
|
cb(store); |
|
}); |
|
} |
|
|
|
function getStore() { |
|
const store = hookStorage.getStore(); |
|
if (!store) { |
|
throw new HookError('[Inquirer] Hook functions can only be called from within a prompt'); |
|
} |
|
return store; |
|
} |
|
export function readline() { |
|
return getStore().rl; |
|
} |
|
|
|
export 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 AsyncResource.bind(wrapped); |
|
} |
|
export 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; |
|
} |
|
export function handleChange() { |
|
getStore().handleChange(); |
|
} |
|
export const 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 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(); |
|
}); |
|
|
|
|
|
store.hooksEffect.length = 0; |
|
})(); |
|
}, |
|
}; |
|
|