feat: export getter and setter types

This commit is contained in:
littensy 2026-03-07 19:32:23 -08:00
parent 3460618ee0
commit 00880bec55
2 changed files with 40 additions and 43 deletions

View file

@ -1,4 +1,5 @@
type Key = string | number | symbol;
type Cleanup = () => void;
export enum ReactiveFlags {
@ -31,11 +32,15 @@ export interface Link {
}
export interface Atom<T> {
(newValue: T | ((currentValue: T) => T)): T;
(update: Update<T>): T;
(): T;
}
export type Setter<T> = (newValue: T | ((currentValue: T) => T)) => T;
export type Getter<T> = () => T;
export type Setter<T> = (update: Update<T>) => T;
export type Update<T> = ((currentValue: T) => T) | T;
export type Equals<T> = (current: T, incoming: T) => boolean;
@ -71,16 +76,8 @@ export const flags: {
* @returns A tuple containing the getter and setter functions for the signal.
* @see https://github.com/littensy/charm?tab=readme-ov-file#signalinitialValue-equals
*/
export function signal<T>(
initialValue: T,
equals?: Equals<T>,
): LuaTuple<[getter: () => T, setter: (newValue: T | ((currentValue: T) => T)) => T]>;
export function signal<T>(): LuaTuple<
[
getter: () => T | undefined,
setter: (newValue: T | undefined | ((currentValue?: T) => T | undefined)) => T | undefined,
]
>;
export function signal<T>(initialValue: T, equals?: Equals<T>): LuaTuple<[getter: Getter<T>, setter: Setter<T>]>;
export function signal<T>(): LuaTuple<[getter: Getter<T | undefined>, setter: Setter<T | undefined>]>;
/**
* Creates a reactive atom that acts as both a signal getter and setter.
@ -102,7 +99,7 @@ export function atom<T>(): Atom<T | undefined>;
* @param getter A function that produces the next value.
* @see https://github.com/littensy/charm?tab=readme-ov-file#computedgetter
*/
export function computed<T>(getter: (previousValue?: T) => T): () => T;
export function computed<T>(getter: (previousValue?: T) => T): Getter<T>;
/**
* Creates an effect that runs a callback in response to signal state changes.
@ -205,7 +202,7 @@ export function batch<Args extends any[], Result>(fn: (...args: Args) => Result,
* @returns A function for disposing the effect.
* @see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback
*/
export function listen<T>(getter: () => T, fn: (value: T, previousValue: T | undefined) => void): Cleanup;
export function listen<T>(getter: Getter<T>, fn: (value: T, previousValue: T | undefined) => void): Cleanup;
/**
* Creates an effect that only runs the callback when the value returned by the
@ -220,7 +217,7 @@ export function listen<T>(getter: () => T, fn: (value: T, previousValue: T | und
* @returns A function for disposing the effect.
* @see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback
*/
export function subscribe<T>(getter: () => T, fn: (value: T, previousValue: T) => void): Cleanup;
export function subscribe<T>(getter: Getter<T>, fn: (value: T, previousValue: T) => void): Cleanup;
/**
* Creates a new read-only signal that is computed by mapping over the entries
@ -232,19 +229,19 @@ export function subscribe<T>(getter: () => T, fn: (value: T, previousValue: T) =
* @see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-mapper
*/
export function mapped<VI, KI, VO, KO = KI>(
getter: () => Map<KI, VI> | ReadonlyMap<KI, VI>,
getter: Getter<Map<KI, VI> | ReadonlyMap<KI, VI>>,
mapper: (value: VI, key: KI) => LuaTuple<[VO, KO]> | VO,
): () => ReadonlyMap<KO, VO>;
): Getter<ReadonlyMap<KO, VO>>;
// Overload for arrays
export function mapped<VI, VO, K extends Key = number>(
getter: () => readonly VI[],
getter: Getter<readonly VI[]>,
mapper: (value: VI, key: number) => LuaTuple<[VO, K]> | VO,
): () => K extends number ? readonly VO[] : { readonly [P in K]: VO };
): Getter<K extends number ? readonly VO[] : { readonly [P in K]: VO }>;
// Overload for objects
export function mapped<VI, KI extends Key, VO, KO extends Key = KI>(
getter: () => { readonly [K in KI]: VI },
getter: Getter<{ readonly [K in KI]: VI }>,
mapper: (value: VI, key: KI) => LuaTuple<[VO, KO]> | VO,
): () => { readonly [K in KO]: VO };
): Getter<{ readonly [K in KO]: VO }>;
/**
* Calls the callback function for each unique key in the table returned by the
@ -263,12 +260,12 @@ export function mapped<VI, KI extends Key, VO, KO extends Key = KI>(
* @see https://github.com/littensy/charm?tab=readme-ov-file#observegetter-callback
*/
export function observe<V, K = number>(
getter: () => Map<K, V> | ReadonlyMap<K, V> | readonly V[],
getter: Getter<Map<K, V> | ReadonlyMap<K, V> | readonly V[]>,
fn: (value: V, key: K) => Cleanup | void,
): Cleanup;
// Overload for objects
export function observe<V, K extends Key>(
getter: () => { readonly [K in Key]: V },
getter: Getter<{ readonly [K in Key]: V }>,
fn: (value: V, key: K) => Cleanup | void,
): Cleanup;

View file

@ -16,9 +16,12 @@ local checkDirty = system.checkDirty
local shallowPropagate = system.shallowPropagate
local createReactiveSystem = system.createReactiveSystem
type Cleanup = () -> ()
export type Atom<T = any> = (...((T) -> T) | T) -> T
export type Getter<T> = () -> T
export type Setter<T> = (update: Update<T>) -> T
export type Atom<T = any> = (...Update<T>) -> T
export type Update<T> = ((current: T) -> T) | T
export type Equals<T> = (current: T, incoming: T) -> boolean
export type Cleanup = () -> ()
type EffectScopeNode = ReactiveNode & {
cleanups: { Cleanup },
@ -412,10 +415,7 @@ end
@return Setter function to update the signal value
@see https://github.com/littensy/charm?tab=readme-ov-file#signalinitialValue-equals
]]
local function signal<T>(
initialValue: T,
equals: ((current: T, incoming: T) -> boolean)?
): (() -> T, (((T) -> T) | T) -> T)
local function signal<T>(initialValue: T, equals: Equals<T>?): (Getter<T>, Setter<T>)
local this: SignalNode = {
currentValue = initialValue,
pendingValue = initialValue,
@ -426,7 +426,7 @@ local function signal<T>(
deepFreeze(initialValue)
end
local function writeSignal(update: ((T) -> T) | T): T
local function writeSignal(update: Update<T>): T
local value: T
-- Determine the new value based on whether the update is a function or
@ -505,7 +505,7 @@ end
@param equals Optional comparator function to check for value changes
@see https://github.com/littensy/charm?tab=readme-ov-file#atominitialValue-equals
]]
local function atom<T>(initialValue: T, equals: ((current: T, incoming: T) -> boolean)?): Atom<T>
local function atom<T>(initialValue: T, equals: Equals<T>?): Atom<T>
local get, set = signal(initialValue, equals)
local function atomOper(...)
return if select("#", ...) == 0 then get() else set((...))
@ -522,7 +522,7 @@ end
@return A function that returns the cached value
@see https://github.com/littensy/charm?tab=readme-ov-file#computedgetter
]]
local function computed<T>(getter: (previousValue: T?) -> T): () -> T
local function computed<T>(getter: (previousValue: T?) -> T): Getter<T>
local this: ComputedNode = {
flags = NONE,
getter = noYield(getter),
@ -784,7 +784,7 @@ end
@param fn A signal or function that accesses signals
@return `fn` wrapped in a computed signal if needed
]]
local function toComputed<T>(fn: () -> T): () -> T
local function toComputed<T>(fn: Getter<T>): Getter<T>
local name = debug.info(fn, "n")
return if name == "readSignal" or name == "readComputed" or name == "atomOper" then fn else computed(fn)
end
@ -802,7 +802,7 @@ end
@return A cleanup function that stops the effect
@see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback
]]
local function listen<T>(getter: () -> T, fn: (state: T, previous: T?) -> ()): Cleanup
local function listen<T>(getter: Getter<T>, fn: (state: T, lastState: T?) -> ()): Cleanup
local tracked = toComputed(getter)
local current: T
@ -826,7 +826,7 @@ end
@return A cleanup function that stops the effect
@see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback
]]
local function subscribe<T>(getter: () -> T, fn: (state: T, lastState: T) -> ()): Cleanup
local function subscribe<T>(getter: Getter<T>, fn: (state: T, lastState: T) -> ()): Cleanup
local tracked = toComputed(getter)
local current: T
local firstRun = true
@ -851,12 +851,12 @@ end
key is removed or the `observe()` effect is disposed. Effects created
during the callback will also be bound to the lifetime of the key.
@param getter Function that returns a table
@param getter A signal or function that returns a table
@param fn Function that is called for each key added to the table
@return A cleanup function that disposes scopes for all keys
@see https://github.com/littensy/charm?tab=readme-ov-file#observegetter-callback
]]
local function observe<K, V>(getter: () -> { [K]: V }, fn: (item: V, key: K) -> ...Cleanup?): Cleanup
local function observe<K, V>(getter: Getter<{ [K]: V }>, fn: (item: V, key: K) -> ...Cleanup?): Cleanup
local innerScopes: { [K]: Cleanup } = {}
local disposed = false
@ -907,11 +907,11 @@ end
of an existing atom. If the key is omitted from the mapper's return value,
the original key is preserved.
@param getter Function that returns a table
@param getter A signal or function that returns a table
@param mapper Function that maps each entry from the source to a new entry in the mapped table
@see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-mapper
]]
local function mapped<KI, VI, KO, VO>(getter: () -> { [KI]: VI }, mapper: (item: VI, key: KI) -> (VO, KO)): () -> { [KO]: VO }
local function mapped<KI, VI, KO, VO>(getter: Getter<{ [KI]: VI }>, mapper: (VI, KI) -> (VO, KO)): Getter<{ [KO]: VO }>
local currentInput: { [KI]: VI } = {}
local keyCache: { [KI]: KO } = {}
@ -984,9 +984,9 @@ return {
listen = listen,
observe = observe,
mapped = mapped :: (<KI, VI, KO, VO>(
getter: () -> { [KI]: VI },
mapper: (item: VI, key: KI) -> (VO, KO)
) -> () -> { [KO]: VO }) & (<K, VI, VO>(getter: () -> { [K]: VI }, mapper: (item: VI, key: K) -> VO) -> () -> { [K]: VO }),
getter: Getter<{ [KI]: VI }>,
mapper: (VI, KI) -> (VO, KO)
) -> Getter<{ [KO]: VO }>) & (<K, VI, VO>(getter: Getter<{ [K]: VI }>, mapper: (VI, K) -> VO) -> Getter<{ [K]: VO }>),
onCleanup = onCleanup,
getActiveSub = getActiveSub,
setActiveSub = setActiveSub,