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 Key = string | number | symbol;
type Cleanup = () => void; type Cleanup = () => void;
export enum ReactiveFlags { export enum ReactiveFlags {
@ -31,11 +32,15 @@ export interface Link {
} }
export interface Atom<T> { export interface Atom<T> {
(newValue: T | ((currentValue: T) => T)): T; (update: Update<T>): 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; 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. * @returns A tuple containing the getter and setter functions for the signal.
* @see https://github.com/littensy/charm?tab=readme-ov-file#signalinitialValue-equals * @see https://github.com/littensy/charm?tab=readme-ov-file#signalinitialValue-equals
*/ */
export function signal<T>( export function signal<T>(initialValue: T, equals?: Equals<T>): LuaTuple<[getter: Getter<T>, setter: Setter<T>]>;
initialValue: T, export function signal<T>(): LuaTuple<[getter: Getter<T | undefined>, setter: Setter<T | undefined>]>;
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,
]
>;
/** /**
* Creates a reactive atom that acts as both a signal getter and setter. * 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. * @param getter A function that produces the next value.
* @see https://github.com/littensy/charm?tab=readme-ov-file#computedgetter * @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. * 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. * @returns A function for disposing the effect.
* @see https://github.com/littensy/charm?tab=readme-ov-file#listengetter-callback * @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 * 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. * @returns A function for disposing the effect.
* @see https://github.com/littensy/charm?tab=readme-ov-file#subscribegetter-callback * @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 * 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 * @see https://github.com/littensy/charm?tab=readme-ov-file#mappedgetter-mapper
*/ */
export function mapped<VI, KI, VO, KO = KI>( 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, mapper: (value: VI, key: KI) => LuaTuple<[VO, KO]> | VO,
): () => ReadonlyMap<KO, VO>; ): Getter<ReadonlyMap<KO, VO>>;
// Overload for arrays // Overload for arrays
export function mapped<VI, VO, K extends Key = number>( 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, 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 // Overload for objects
export function mapped<VI, KI extends Key, VO, KO extends Key = KI>( 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, 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 * 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 * @see https://github.com/littensy/charm?tab=readme-ov-file#observegetter-callback
*/ */
export function observe<V, K = number>( 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, fn: (value: V, key: K) => Cleanup | void,
): Cleanup; ): Cleanup;
// Overload for objects // Overload for objects
export function observe<V, K extends Key>( 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, fn: (value: V, key: K) => Cleanup | void,
): Cleanup; ): Cleanup;

View file

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