Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bolt v4 #2254

Merged
merged 55 commits into from
Oct 17, 2024
Merged

bolt v4 #2254

merged 55 commits into from
Oct 17, 2024

Conversation

filmaj
Copy link
Contributor

@filmaj filmaj commented Sep 13, 2024

Summary

bolt-js v4 release PR. Breaking changes are planned for this release!

This PR is currently published under the 4.0.0-rc.4 version on npm. Feel free to give it a spin and give us feedback!

Breaking Changes

Middleware Type Changes

This one I'm particularly excited about! In bolt we have a set of Slack*MiddlewareArgs types: for events, shortcuts, commands, and so on. They 'wrap' the underlying event payloads with additional middleware-relevant bits like a next() method, a context object for devs to augment, and so on.

Many of these types, for example the SlackEventMiddlewareArgs type, previously used a conditional to sometimes define particular additional helper utilities on the middleware arguments. For example, the say utility, or tacking on a convenience message property for message-event-related payloads. This was problematic in practice in TypeScript situations, not just internally (this change fixes #2135) within the bolt codebase but for developers as well: when the payload was not of a type that required the extra utility, these properties would be required to exist on the middleware arguments but have a value of undefined. Those of us trying to build generic middleware utilities would have to deal with TS compilation errors and needing to liberally type-cast to avoid these conditional mismatches with undefined.

Instead, these MiddlewareArgs types now conditionally create a type intersection when appropriate in order to provide this conditional-utility-extension mechanism. In practice that looks something like:

type SomeMiddlewareArgs<EventType extends string = string> = {
  // some type in here
} & (EventType extends 'message'
  // If this is a message event, add a `message` property
  ? { message: EventFromType<EventType> }
  : unknown
)

With the above, now when a message payload is wrapped up into middleware arguments, it will contain an appropriate message property, whereas a non-message payload will be intersected with unknown - effectively a type "noop." No more e.g. say: undefined or message: undefined to deal with!

Other Breaking Changes

Non-breaking Changes

  • try some cleanup and a bit of modernization in tsconfig.json
    • since we do use fallthrough in switch statements, removed that from tsconfig.json; the alternative of using @ts-ignore comments in places where we do is dangerous, as non-fallthrough-in-switch type errors are also ignored from it
  • adds a type predicate for asserting a fulfilled vs. rejected promise (for use in array methods like map)
  • remove some unnecessary type casts in processEvent
  • expose the bundled @slack/web-api dependency under the webApi named export
  • dependency updates:
    • upgrades raw-body to v3
    • upgrades @slack/oauth to v3
    • removes promise.allsettled since that is natively supported in node since v14
    • moves @types/tsscmp to dev dependencies since that is not exposed to developers

TODO

Post-release TODO

  • update all examples and sample dependencies to use bolt v4

@filmaj filmaj added semver:major draft A pull request that is currently in active development and not yet ready for review labels Sep 13, 2024
@filmaj filmaj added this to the 4.0.0 milestone Sep 13, 2024
@filmaj filmaj self-assigned this Sep 13, 2024
Copy link

codecov bot commented Sep 13, 2024

Codecov Report

Attention: Patch coverage is 95.06726% with 66 lines in your changes missing coverage. Please review.

Project coverage is 92.47%. Comparing base (1ebd657) to head (a954a40).
Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
src/App.ts 92.77% 18 Missing ⚠️
src/receivers/HTTPModuleFunctions.ts 91.14% 17 Missing ⚠️
src/receivers/HTTPReceiver.ts 81.08% 14 Missing ⚠️
src/Assistant.ts 92.85% 5 Missing ⚠️
src/receivers/AwsLambdaReceiver.ts 93.58% 5 Missing ⚠️
src/middleware/builtin.ts 97.05% 3 Missing and 1 partial ⚠️
src/AssistantThreadContextStore.ts 87.50% 1 Missing ⚠️
src/receivers/ExpressReceiver.ts 98.70% 1 Missing ⚠️
src/receivers/SocketModeReceiver.ts 97.36% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##             main    #2254       +/-   ##
===========================================
+ Coverage   81.21%   92.47%   +11.26%     
===========================================
  Files          21       36       +15     
  Lines        1799     7453     +5654     
  Branches      498      646      +148     
===========================================
+ Hits         1461     6892     +5431     
- Misses        218      553      +335     
+ Partials      120        8      -112     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

@filmaj filmaj marked this pull request as ready for review September 27, 2024 17:50
@filmaj filmaj changed the title v4 draft bolt v4 Sep 27, 2024
@filmaj filmaj removed the draft A pull request that is currently in active development and not yet ready for review label Sep 27, 2024
// Filter out any non-actions
if (action === undefined) {
return;
export const onlyActions: Middleware<AnyMiddlewareArgs> = async (args) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Breaking change: no longer need to call this as onlyActions(), instead, it is a middleware itself and can be simply passed as onlyActions. The wrapping closure within was unnecessary. This lines it up with other, non-parameterized middleware in this module.

if (action === undefined) {
return;
export const onlyActions: Middleware<AnyMiddlewareArgs> = async (args) => {
if ('action' in args && args.action) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of casting, in TS we should be using more of 'property' in object style checks and let TS do the type narrowing for us.

// Filter out any non-shortcuts
if (shortcut === undefined) {
return;
export const onlyShortcuts: Middleware<AnyMiddlewareArgs> = async (args) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another breaking change: this is a middleware, no need for extra internal closure.

// Filter out any non-commands
if (command === undefined) {
return;
export const onlyCommands: Middleware<AnyMiddlewareArgs> = async (args) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same deal! This is middleware, remove internal closure.

// Filter out any non-options requests
if (options === undefined) {
return;
export const onlyOptions: Middleware<AnyMiddlewareArgs> = async (args) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Broken record: middleware, remove internal closure.

// Filter out any non-events
if (event === undefined) {
return;
export const onlyEvents: Middleware<AnyMiddlewareArgs> = async (args) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You guessed it: middleware, 🔪 closure

// Filter out anything that doesn't have a view
if (view === undefined) {
return;
export const onlyViewActions: Middleware<AnyMiddlewareArgs> = async (args) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Samesame

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 🧠 Big fan of it all!

return;
}
}
export const ignoreSelf: Middleware<AnyMiddlewareArgs> = async (args) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same: 🔪 internal closure since we don't use any of the outer function's parameters.

if (isMessageEventArgs(args)) {
const { message } = args;
// Look for an event that is identified as a bot message from the same bot ID as this app, and return to skip
if (message.subtype === 'bot_message' && message.bot_id === botId) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The combination of cleaning up the middleware types and use of type predicates lets us clean up the type casting and simplify the logic in this method ❤️

| GlobalShortcut
| InteractiveMessageSuggestion
| DialogSuggestion;
export const directMention: Middleware<SlackEventMiddlewareArgs<'message'>> = async ({ message, context, next }) => {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔪 closure, straight middleware

@@ -529,7 +529,7 @@ describe('ExpressReceiver', function () {
// Act
const req = { body: { }, url: 'http://localhost/slack/oauth_redirect', method: 'GET' } as Request;
const resp = { send: () => { } } as Response;
(receiver.router as any).handle(req, resp);
(receiver.router as any).handle(req, resp, () => {});
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A change that was required as part of the move from express v4 -> v5

@filmaj filmaj requested a review from a team September 27, 2024 18:18
Copy link
Member

@seratch seratch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work; Left a few comments!

src/receivers/AwsLambdaReceiver.ts Outdated Show resolved Hide resolved
src/receivers/AwsLambdaReceiver.ts Outdated Show resolved Hide resolved
src/receivers/AwsLambdaReceiver.ts Outdated Show resolved Hide resolved
tsconfig.json Show resolved Hide resolved
filmaj and others added 21 commits October 17, 2024 10:07
…e invoked as a method to return middleware; instead, they are directly middleware, like other built-in middleware functions.
…e `message` parameter. THIS FIXES SO MUCH! shouts to jcalz on stackoverflow
…ntMiddlewareArgs when event contains channel information.
…ionMiddlewareArgs when a non-dialog or step from app action
…rtcutMiddlewareArgs when a non-dialog or step from app action. also TODOs
@filmaj filmaj merged commit 91750c2 into main Oct 17, 2024
20 checks passed
@filmaj filmaj deleted the bolt-four branch October 17, 2024 19:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment