--[[ Implements a form of dependency injection to save the need from passing data as props through intermediate components. ]] export type Context = { default_value: T, _values: {[thread]: T}, provide: (Context, callback: () -> U) -> (new: T) -> U, consume: (Context) -> T } type ContextNoDefault = { _values: {[thread]: T}, provide: (Context, callback: () -> U) -> (new: T) -> U, consume: (Context) -> T? } local function provide(context: Context, callback: () -> U) return function(new: T): U local thread = coroutine.running() local old = context._values[thread] context._values[thread] = new local ok, value = pcall(callback) context._values[thread] = old if not ok then error(`provided callback errored with "{value}"`, 2) end return value end :: (new: T) -> U end local function consume(context: Context): T local thread = coroutine.running() return context._values[thread] or context.default_value end local function create_context(default_value: T?): Context return { default_value = default_value, _values = {}, provide = provide :: any, consume = consume } end return create_context :: ((default_value: T) -> Context) & (() -> ContextNoDefault)