Through the ongoing discussion in the comments, a more efficient solution has emerged for addressing this particular issue. This insight was gained from this shared by jcalz.
It all starts with:
undefined
is strictly prohibited
- The function signature depends on the generic type
Initial Action
Replicated the Option setup from Rust.
This approach helps in eliminating undefined values for optional types.
export type Option<T> = {
isNone(): boolean;
isSome(): boolean;
unwrap(): T;
unwrapOr(alternativeOpt: T): T;
expect(msg: string): T;
};
export const option = <T>(opt?: T): Option<T> => {
const isNone = () => opt === undefined || opt === null;
const isSome = () => !isNone();
const unwrap = () => {
if (isNone()) throw new Error('option is none');
return opt as T;
};
const unwrapOr = (alternativeOpt: T) => {
if (isSome()) return unwrap();
return alternativeOpt as T;
};
const expect = (msg: string) => {
if (isSome()) return unwrap();
throw new Error(msg);
};
return { isNone, isSome, unwrap, unwrapOr, expect };
};
Next Step
Altered the type for the distribution implementation to align with the function signature depending on the generic type and transitioned options into the Option<T>
structure.
This resolution was made achievable with insights from this
Credits to jcalz
export type DistributorOn<T> = <K extends keyof T>(
key: K extends string ? K : never,
listener: T[K] extends (...args: never[]) => void
? T[K]
: (value: T[K] extends NonNullable<T[K]> ? T[K] : Option<Exclude<T[K], null | undefined>>) => void,
) => DistributorListener;
export type DistributorSend<T> = <K extends keyof T>(
key: K extends string ? K : never,
...rest: T[K] extends () => void
? [value?: never]
: T[K] extends (...args: never[]) => void
? Parameters<T[K]>
: T[K] extends NonNullable<T[K]>
? [value: T[K]]
: [value: Option<Exclude<T[K], null | undefined>>]
) => void;
export type Distributor<T> = {
on: DistributorOn<T>;
send: DistributorSend<T>;
};
Final Step
Test it out
type MyDistrobution = {
name: string;
age?: number;
hook: () => void;
msg: (subject: string, msg: string) => void;
};
const myDistro = distributor<MyDistrobution>();
// for MyDistrobution.name
myDistro.on('name', (name) => consoe.log(name); // <-- will be Marie Juana
myDistro.send('name', 'Marie Juana');
// for MyDistrobution.age
myDistro.on('age', (age) => console.log(age.isNone()); // <-- will be true
myDistro.send('age', option());
myDistro.on('age', (age) => consolelog(age.isSome())); // <-- will be false
myDistro.send('age', option(123));
// for MyDistrobution.hook
myDistro.on('hook', () => console.log('called'));
myDistro.send('hook');
// for MyDistrobution.msg
myDistro.on('msg', console.log); // <-- will be foobar, hello world
myDistro.send('msg', 'foobar', 'hello world');