Skip to content

Instantly share code, notes, and snippets.

@polerin
Last active May 23, 2022 23:37
Show Gist options
  • Save polerin/28b598e9f097dde63df57b8e90160dc3 to your computer and use it in GitHub Desktop.
Save polerin/28b598e9f097dde63df57b8e90160dc3 to your computer and use it in GitHub Desktop.
Typescript Type mapping / const / etc issues
import MessageBridge from "./MessageBridge";
import { AppMessageMap, AppMessageNames as messageNames } from "./MessageMap";
const bridge = new MessageBridge<AppMessageMap>();
// Desired functionality is something like this?
// * easily accessible const/symbol/something defining the message name (no magic string)
// * easily accessible type definition for the message (don't have to go around the world to look it up)
bridge.subscribe(messageNames.TwitchRaid, (AppMessageMap[TwitchRaid]) => { /* ... */ });
// * nice to have: an empty message factory, but that's not a requirement and I know it might be harder?
const raidMessage : AppMessageMap[messageNames.TwitchRaid] = {
messageType: 'systemMessage',
raidFrom : "SomeUser",
raidSize : 42
};
bridge.publish(messageNames.TwitchRaid, raidMessage);
// Basically just a typed facade around PubSub, but the basic goal
// is the same regardless of the backing implmentation
export default class MessageBridge<MessageMap>
{
// ... snip
public subscribe<MessageName extends keyof MessageMap>(
messageName : MessageName,
listener : (message : MessageMap[MessageName]) => void
} {
// ... snip
}
public publish<MessageName extends keyof MessageMap>(
messageName : MessageName,
message : MessageMap[MessageName]
} {
// ... snip
}
// Nice to have, donno if feasible
public buildMessage<MessageName extends keyof MessageMap>(
messageName : MessageName,
data? : any
) : MessageMap[MessageName] {
/* build and return an empty message of type, prefilled with the params? Partial<>? */
}
}
// This file has the part I don't know how to do
// question at bottom, after context
type MessageBase = {
messageType: string;
shortName: string;
};
export type SystemMessage = MessageBase | {
messageType: "systemMessage";
};
export type TwitchMessage = MessageBase | {
messageType: "twitchMessage";
};
export type ActionRequest = MessageBase | {
messageType: "actionRequest";
};
// I'm planning on the below using type merging to keep the
// definitions for each message category managable.
// excluded for example clarity.
interface AppMessageSource {
'obs.websocket.connected' : SystemMessage | {
shortName: "ObsConnected";
};
'obs.websocket.disconnected' : SystemMessage | {
shortName: "ObsDisconnected";
};
// ... snip
'twitch.event.raid' : TwitchMessage | {
shortName: "TwitchRaid";
raidFrom: string;
raidSize: number;
};
'twitch.event.follow': TwitchMessage | {
shortName: "TwitchFollow";
newFollowerUsername: string;
};
// ... snip
'overlay.request.changeFilter': {
shortName: "OverlayChangeFilter";
source : string;
filterName : string;
paramChanges : {[paramName : string] : boolean | string | number};
}
};
// Something like this?
type TrimmedMap<SourceMap> = {
[Property in keyof SourceMap]: Omit<SourceMap[Property], "shortName">
};
// Actual used map?
export type AppMessageMap : TrimmedMap<AppMessageSource>;
// Here is the parts I don't understand how to do. I want to automatically
// build an easy to snag listing of shortname -> full message name
// using the properties in the map source. Consts, Map object(s), or even a function
// that returns the full name based on type would be fine.
// I just really really don't want a whole bunch of magic strings running
// around.
// Something like this would be awesome, except not manually constructed.
export const AppMessageNames = {
ObsConnected: 'obs.websocket.connected',
ObsDisconnected: 'obs.websocket.disconnected',
// ... snip,
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment