Skip to content

Commit

Permalink
Add type parameter of event subtype for app.message listeners #796
Browse files Browse the repository at this point in the history
  • Loading branch information
seratch committed Apr 9, 2021
1 parent 4365f45 commit 0fce896
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 5 deletions.
14 changes: 11 additions & 3 deletions src/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
SlackActionMiddlewareArgs,
SlackCommandMiddlewareArgs,
SlackEventMiddlewareArgs,
SlackSubtypedEventMiddlewareArgs,
SlackOptionsMiddlewareArgs,
SlackShortcutMiddlewareArgs,
SlackViewMiddlewareArgs,
Expand Down Expand Up @@ -470,9 +471,16 @@ export default class App {

// TODO: just make a type alias for Middleware<SlackEventMiddlewareArgs<'message'>>
// TODO: maybe remove the first two overloads
public message(...listeners: Middleware<SlackEventMiddlewareArgs<'message'>>[]): void;
public message(pattern: string | RegExp, ...listeners: Middleware<SlackEventMiddlewareArgs<'message'>>[]): void;
public message(...patternsOrMiddleware: (string | RegExp | Middleware<SlackEventMiddlewareArgs<'message'>>)[]): void {
public message<Subtype extends string | undefined = '*'>(
...listeners: Middleware<SlackSubtypedEventMiddlewareArgs<'message', Subtype>>[]
): void;
public message<Subtype extends string | undefined = '*'>(
pattern: string | RegExp,
...listeners: Middleware<SlackSubtypedEventMiddlewareArgs<'message', Subtype>>[]
): void;
public message<Subtype extends string | undefined = '*'>(
...patternsOrMiddleware: (string | RegExp | Middleware<SlackSubtypedEventMiddlewareArgs<'message', Subtype>>)[]
): void {
const messageMiddleware = patternsOrMiddleware.map((patternOrMiddleware) => {
if (typeof patternOrMiddleware === 'string' || util.types.isRegExp(patternOrMiddleware)) {
return matchMessage(patternOrMiddleware);
Expand Down
22 changes: 22 additions & 0 deletions src/types/events/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from './base-events';
export {
MessageEvent as AllMessageEvent,
BotMessageEvent,
GenericMessageEvent,
MessageRepliedEvent,
Expand All @@ -24,6 +25,17 @@ export interface SlackEventMiddlewareArgs<EventType extends string = string> {
say: WhenEventHasChannelContext<this['payload'], SayFn>;
}

export interface SlackSubtypedEventMiddlewareArgs<
EventType extends string = string,
EventSubtype extends string | undefined = undefined
> {
payload: EventSubtype extends '*' ? EventFromType<EventType> : EventFromTypeAndSubtype<EventType, EventSubtype>;
event: this['payload'];
message: EventType extends 'message' ? this['payload'] : never;
body: EnvelopedEvent<this['payload']>;
say: WhenEventHasChannelContext<this['payload'], SayFn>;
}

/**
* A Slack Events API event wrapped in the standard envelope.
*
Expand Down Expand Up @@ -60,7 +72,17 @@ interface Authorization {
* Otherwise, the `BasicSlackEvent<T>` type is returned.
*/
type EventFromType<T extends string> = KnownEventFromType<T> extends never ? BasicSlackEvent<T> : KnownEventFromType<T>;
type EventFromTypeAndSubtype<T extends string, ST extends string | undefined> = KnownEventFromTypeAndSubtype<
T,
ST
> extends never
? BasicSlackEvent<T>
: KnownEventFromTypeAndSubtype<T, ST>;
type KnownEventFromType<T extends string> = Extract<SlackEvent, { type: T }>;
type KnownEventFromTypeAndSubtype<T extends string, ST extends string | undefined> = Extract<
SlackEvent,
{ type: T; subtype: ST }
>;

/**
* Type function which tests whether or not the given `Event` contains a channel ID context for where the event
Expand Down
28 changes: 26 additions & 2 deletions types-tests/message.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ import { App, MessageEvent, GenericMessageEvent, BotMessageEvent, MessageReplied
const app = new App({ token: 'TOKEN', signingSecret: 'Signing Secret' });

expectType<void>(
// TODO: Resolve the event type when having subtype in a listener constraint
// app.message({pattern: 'foo', subtype: 'message_replied'}, async ({ message }) => {});
app.message(async ({ message }) => {
expectType<MessageEvent>(message);

Expand Down Expand Up @@ -70,3 +68,29 @@ expectType<void>(
await Promise.resolve(message);
}),
);

// Resolve the event type when having subtype in a listener constraint
// app.message<'message_replied'>('foo', async ({ message }) => {});

expectType<void>(
app.message<'thread_broadcast'>('foo', async ({ message }) => {
expectNotType<MessageEvent>(message);
expectType<ThreadBroadcastMessageEvent>(message);
message.channel; // the property access should compile
message.thread_ts; // the property access should compile
message.ts; // the property access should compile
message.root; // the property access should compile
await Promise.resolve(message);
}),
);
expectType<void>(
// no subtype in the event payload
app.message<undefined>('foo', async ({ message }) => {
expectNotType<MessageEvent>(message);
expectType<GenericMessageEvent>(message);
message.user; // the property access should compile
message.channel; // the property access should compile
message.team; // the property access should compile
await Promise.resolve(message);
}),
);

0 comments on commit 0fce896

Please sign in to comment.