From c5fa3573a8158b866bec1b501f56dd534ccdef3c Mon Sep 17 00:00:00 2001 From: Brad Harris Date: Tue, 13 Sep 2022 15:21:32 -0600 Subject: [PATCH] Add a client instance to function handler params (#101) * setting up client as new param passed into all function handlers * adding enrichContext to centralize where it's done across handlers * updating api version for testing * using published version of api * updating types * removing type * new deno version fmt adjustments * updating tests, adding client to unhandledEvent handlers * checking for client in unhandledEvent handler * adding enrichContext tests * updating exported SlackFunctionHandler type to represent runtime context and not enriched SlackFunction() handler context * switching to ts-expect-error * export SlackAPI * adding some api exports --- build_npm.ts | 2 +- docs/functions-action-handlers.md | 72 +++-- docs/functions-view-handlers.md | 18 +- src/deps.ts | 5 + .../base_function_handler_type_test.ts | 62 +++-- src/functions/enrich-context.ts | 22 ++ src/functions/enrich-context_test.ts | 33 +++ src/functions/interactivity/action_router.ts | 32 ++- .../interactivity/action_router_test.ts | 7 +- src/functions/interactivity/types.ts | 23 +- src/functions/interactivity/view_router.ts | 18 +- .../interactivity/view_router_test.ts | 13 +- src/functions/slack-function.ts | 36 ++- src/functions/slack-function_test.ts | 40 ++- .../slack_function_handler_type_test.ts | 254 ++++++++++-------- src/functions/tester/mod.ts | 5 +- src/functions/types.ts | 102 ++++++- src/mod.ts | 1 + src/types.ts | 12 +- 19 files changed, 519 insertions(+), 238 deletions(-) create mode 100644 src/deps.ts create mode 100644 src/functions/enrich-context.ts create mode 100644 src/functions/enrich-context_test.ts diff --git a/build_npm.ts b/build_npm.ts index 4b31f815..7ff96884 100644 --- a/build_npm.ts +++ b/build_npm.ts @@ -27,4 +27,4 @@ await build({ }); // post build steps -Deno.copyFileSync("README.md", "npm/README.md"); \ No newline at end of file +Deno.copyFileSync("README.md", "npm/README.md"); diff --git a/docs/functions-action-handlers.md b/docs/functions-action-handlers.md index c14ca587..ea649a8b 100644 --- a/docs/functions-action-handlers.md +++ b/docs/functions-action-handlers.md @@ -89,49 +89,44 @@ subdirectory in your app) that posts a message with two buttons: an approval but and a deny button: ```typescript -import type { SlackFunctionHandler } from "deno-slack-sdk/types.ts"; -import { SlackAPI } from "deno-slack-api/mod.ts"; +import { SlackFunction } from "deno-slack-sdk/mod.ts"; // ApprovalFunction is the function we defined in the previous section import { ApprovalFunction } from "./definition.ts"; -const approval: SlackFunctionHandler = - async ({ inputs, token }) => { - console.log('Incoming approval!'); - // A Slack API client, so that we can make API calls to Slack - const client = SlackAPI(token); - - await client.chat.postMessage({ - channel: inputs.approval_channel_id, - blocks: [{ - "type": "actions", - "block_id": "mah-buttons", - "elements": [{ - type: "button", - text: { - type: "plain_text", - text: "Approve", - }, - action_id: "approve_request", - style: "primary", +export default SlackFunction(ApprovalFunction, async ({ inputs, client }) => { + console.log('Incoming approval!'); + + await client.chat.postMessage({ + channel: inputs.approval_channel_id, + blocks: [{ + "type": "actions", + "block_id": "mah-buttons", + "elements": [{ + type: "button", + text: { + type: "plain_text", + text: "Approve", }, - { - type: "button", - text: { - type: "plain_text", - text: "Deny", - }, - action_id: "deny_request", - style: "danger", - }], + action_id: "approve_request", + style: "primary", + }, + { + type: "button", + text: { + type: "plain_text", + text: "Deny", + }, + action_id: "deny_request", + style: "danger", }], - }); - // Important to set completed: false! We will set the function's complete - // status later - in our action handler - return { - completed: false, - }; + }], + }); + // Important to set completed: false! We will set the function's complete + // status later - in our action handler + return { + completed: false, }; -export default approval; +}); ``` The key bit of information we need to remember before moving on to adding an @@ -161,9 +156,8 @@ const ActionsRouter = BlockActionsRouter(ApprovalFunction); export const blockActions = ActionsRouter.addHandler( ['approve_request', 'deny_request'], // The first argument to addHandler can accept an array of action_id strings, among many other formats! // Check the API reference at the end of this document for the full list of supported options - async ({ action, body, token }) => { // The second argument is the handler function itself + async ({ action, body, client }) => { // The second argument is the handler function itself console.log('Incoming action handler invocation', action); - const client = SlackAPI(token); const outputs = { reviewer: body.user.id, diff --git a/docs/functions-view-handlers.md b/docs/functions-view-handlers.md index ee36e5d1..1085a7cb 100644 --- a/docs/functions-view-handlers.md +++ b/docs/functions-view-handlers.md @@ -99,17 +99,12 @@ type `Schema.slack.types.interactivity` under the `interactivity_pointer` proper Check out the code below for an example: ```typescript -import type { SlackFunctionHandler } from "deno-slack-sdk/types.ts"; -import { SlackAPI } from "deno-slack-api/mod.ts"; +import { SlackFunction } from "deno-slack-sdk/mod.ts"; // DiaryFunction is the function we defined in the previous section import { DiaryFunction } from "./definition.ts"; -const diary: SlackFunctionHandler< - typeof DiaryFunction.definition -> = async ({ inputs, token }) => { +export default SlackFunction(DiaryFunction, async ({ inputs, client }) => { console.log('Someone might want to write a diary entry...'); - // A Slack API client, so that we can make API calls to Slack - const client = SlackAPI(token); await client.views.open({ trigger_id: inputs.interactivity.interactivity_pointer, @@ -182,15 +177,13 @@ You can use the value of `body.trigger_id` within an action handler to open a view, like so: ```typescript -import type { SlackFunctionHandler } from "deno-slack-sdk/types.ts"; -import { SlackAPI } from "deno-slack-api/mod.ts"; +import { BlockActionsRouter } from "deno-slack-sdk/mod.ts"; // DiaryFunction is the function we defined in the previous section import { DiaryFunction } from "./definition.ts"; export const blockActions = BlockActionsRouter(DiaryFunction).addHandler( 'deny_request', - async ({ action, body, token }) => { - const client = SlackAPI(token); + async ({ action, body, client }) => { await client.views.open({ trigger_id: body.trigger_id, view: { /* your view object goes here */ }, @@ -252,8 +245,7 @@ As an example, consider the following two code snippets. They yield identical be ```typescript export const { viewSubmission, viewClosed } = ViewRouter // A view submission handler that pushes a new view using the API - .addSubmissionHandler(/view/, async ({ token, body }) => { - const client = SlackAPI(token); + .addSubmissionHandler(/view/, async ({ client, body }) => { await client.views.push({ trigger_id: body.trigger_id, view: { /* your view object goes here */ }, diff --git a/src/deps.ts b/src/deps.ts new file mode 100644 index 00000000..ea29e55a --- /dev/null +++ b/src/deps.ts @@ -0,0 +1,5 @@ +export { SlackAPI } from "https://deno.land/x/deno_slack_api@1.1.0/mod.ts"; +export type { + SlackAPIClient, + Trigger, +} from "https://deno.land/x/deno_slack_api@1.1.0/types.ts"; diff --git a/src/functions/base_function_handler_type_test.ts b/src/functions/base_function_handler_type_test.ts index b66538d5..084fadf4 100644 --- a/src/functions/base_function_handler_type_test.ts +++ b/src/functions/base_function_handler_type_test.ts @@ -1,18 +1,18 @@ import { assertEquals } from "../dev_deps.ts"; import { SlackFunctionTester } from "./tester/mod.ts"; -import { BaseSlackFunctionHandler } from "./types.ts"; +import { BaseRuntimeSlackFunctionHandler } from "./types.ts"; // These tests are to ensure our Function Handler types are supporting the use cases we want to // Any "failures" here will most likely be reflected in Type errors -Deno.test("BaseSlackFunctionHandler types", () => { +Deno.test("BaseRuntimeSlackFunctionHandler types", () => { type Inputs = { in: string; }; type Outputs = { out: string; }; - const handler: BaseSlackFunctionHandler = ( + const handler: BaseRuntimeSlackFunctionHandler = ( { inputs }, ) => { return { @@ -27,10 +27,10 @@ Deno.test("BaseSlackFunctionHandler types", () => { assertEquals(result.outputs?.out, inputs.in); }); -Deno.test("BaseSlackFunctionHandler with empty inputs and empty outputs", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with empty inputs and empty outputs", () => { type Inputs = Record; type Outputs = Record; - const handler: BaseSlackFunctionHandler = () => { + const handler: BaseRuntimeSlackFunctionHandler = () => { return { outputs: {}, }; @@ -40,10 +40,10 @@ Deno.test("BaseSlackFunctionHandler with empty inputs and empty outputs", () => assertEquals(result.outputs, {}); }); -Deno.test("BaseSlackFunctionHandler with undefined inputs and outputs", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with undefined inputs and outputs", () => { type Inputs = undefined; type Outputs = undefined; - const handler: BaseSlackFunctionHandler = () => { + const handler: BaseRuntimeSlackFunctionHandler = () => { return { outputs: {}, }; @@ -53,12 +53,14 @@ Deno.test("BaseSlackFunctionHandler with undefined inputs and outputs", () => { assertEquals(result.outputs, {}); }); -Deno.test("BaseSlackFunctionHandler with inputs and empty outputs", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with inputs and empty outputs", () => { type Inputs = { in: string; }; type Outputs = Record; - const handler: BaseSlackFunctionHandler = ({ inputs }) => { + const handler: BaseRuntimeSlackFunctionHandler = ( + { inputs }, + ) => { const _test = inputs.in; return { @@ -71,12 +73,12 @@ Deno.test("BaseSlackFunctionHandler with inputs and empty outputs", () => { assertEquals(result.outputs, {}); }); -Deno.test("BaseSlackFunctionHandler with empty inputs and outputs", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with empty inputs and outputs", () => { type Inputs = Record; type Outputs = { out: string; }; - const handler: BaseSlackFunctionHandler = () => { + const handler: BaseRuntimeSlackFunctionHandler = () => { return { outputs: { out: "test", @@ -88,9 +90,9 @@ Deno.test("BaseSlackFunctionHandler with empty inputs and outputs", () => { assertEquals(result.outputs?.out, "test"); }); -Deno.test("BaseSlackFunctionHandler with any inputs and any outputs", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with any inputs and any outputs", () => { // deno-lint-ignore no-explicit-any - const handler: BaseSlackFunctionHandler = ({ inputs }) => { + const handler: BaseRuntimeSlackFunctionHandler = ({ inputs }) => { return { outputs: { out: inputs.in, @@ -103,36 +105,40 @@ Deno.test("BaseSlackFunctionHandler with any inputs and any outputs", () => { assertEquals(result.outputs?.out, inputs.in); }); -Deno.test("BaseSlackFunctionHandler with no inputs and error output", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with no inputs and error output", () => { // deno-lint-ignore no-explicit-any - const handler: BaseSlackFunctionHandler = () => { - return { - error: "error", + const handler: BaseRuntimeSlackFunctionHandler = + () => { + return { + error: "error", + }; }; - }; const { createContext } = SlackFunctionTester("test"); const result = handler(createContext({ inputs: {} })); assertEquals(result.error, "error"); }); -Deno.test("BaseSlackFunctionHandler with no inputs and completed false output", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with no inputs and completed false output", () => { // deno-lint-ignore no-explicit-any - const handler: BaseSlackFunctionHandler = () => { - return { - completed: false, + const handler: BaseRuntimeSlackFunctionHandler = + () => { + return { + completed: false, + }; }; - }; const { createContext } = SlackFunctionTester("test"); const result = handler(createContext({ inputs: {} })); assertEquals(result.completed, false); }); -Deno.test("BaseSlackFunctionHandler with set inputs and any outputs", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with set inputs and any outputs", () => { type Inputs = { in: string; }; // deno-lint-ignore no-explicit-any - const handler: BaseSlackFunctionHandler = ({ inputs }) => { + const handler: BaseRuntimeSlackFunctionHandler = ( + { inputs }, + ) => { return { outputs: { out: inputs.in, @@ -145,7 +151,7 @@ Deno.test("BaseSlackFunctionHandler with set inputs and any outputs", () => { assertEquals(result.outputs?.out, inputs.in); }); -Deno.test("BaseSlackFunctionHandler with input and output objects", () => { +Deno.test("BaseRuntimeSlackFunctionHandler with input and output objects", () => { type Inputs = { anObject: { in: string; @@ -156,7 +162,9 @@ Deno.test("BaseSlackFunctionHandler with input and output objects", () => { out: string; }; }; - const handler: BaseSlackFunctionHandler = ({ inputs }) => { + const handler: BaseRuntimeSlackFunctionHandler = ( + { inputs }, + ) => { return { outputs: { anObject: { out: inputs.anObject.in }, diff --git a/src/functions/enrich-context.ts b/src/functions/enrich-context.ts new file mode 100644 index 00000000..275048e0 --- /dev/null +++ b/src/functions/enrich-context.ts @@ -0,0 +1,22 @@ +import { SlackAPI } from "../deps.ts"; +import { + BaseRuntimeFunctionContext, + FunctionContextEnrichment, +} from "./types.ts"; + +export const enrichContext = ( + // deno-lint-ignore no-explicit-any + context: BaseRuntimeFunctionContext, +): typeof context & FunctionContextEnrichment => { + const token = context.token; + const slackApiUrl = (context.env || {})["SLACK_API_URL"]; + + const client = SlackAPI(token, { + slackApiUrl: slackApiUrl ? slackApiUrl : undefined, + }); + + return { + ...context, + client, + }; +}; diff --git a/src/functions/enrich-context_test.ts b/src/functions/enrich-context_test.ts new file mode 100644 index 00000000..e67145fe --- /dev/null +++ b/src/functions/enrich-context_test.ts @@ -0,0 +1,33 @@ +import { assertExists } from "../dev_deps.ts"; +import { enrichContext } from "./enrich-context.ts"; +import { BaseRuntimeFunctionContext } from "./types.ts"; + +Deno.test("enrichContext with no env.SLACK_API_URL", () => { + // deno-lint-ignore no-explicit-any + const ctx: BaseRuntimeFunctionContext = { + env: {}, + inputs: {}, + team_id: "team", + token: "token", + }; + + const newContext = enrichContext(ctx); + + assertExists(newContext.client); +}); + +Deno.test("enrichContext with env.SLACK_API_URL", () => { + // deno-lint-ignore no-explicit-any + const ctx: BaseRuntimeFunctionContext = { + env: { + "SLACK_API_URL": "https://something.slack.com/api", + }, + inputs: {}, + team_id: "team", + token: "token", + }; + + const newContext = enrichContext(ctx); + + assertExists(newContext.client); +}); diff --git a/src/functions/interactivity/action_router.ts b/src/functions/interactivity/action_router.ts index 3c8bf049..42e61971 100644 --- a/src/functions/interactivity/action_router.ts +++ b/src/functions/interactivity/action_router.ts @@ -4,8 +4,14 @@ import { } from "../../parameters/mod.ts"; import { SlackFunctionDefinition } from "../mod.ts"; import { UnhandledEventError } from "../unhandled-event-error.ts"; -import { FunctionDefinitionArgs } from "../types.ts"; -import type { BlockActionConstraint, BlockActionHandler } from "./types.ts"; +import { enrichContext } from "../enrich-context.ts"; +import { FunctionDefinitionArgs, FunctionRuntimeParameters } from "../types.ts"; +import type { + ActionContext, + BlockActionConstraint, + BlockActionHandler, + RuntimeActionContext, +} from "./types.ts"; import { BlockAction } from "./block_actions_types.ts"; import { matchBasicConstraintField, @@ -98,15 +104,12 @@ export class ActionsRouter< * Returns a method handling routing of action payloads to the appropriate action handler. * The output of export() should be attached to the `blockActions` export of your function. */ - export(): BlockActionHandler< - FunctionDefinitionArgs< - InputParameters, - OutputParameters, - RequiredInput, - RequiredOutput - > - > { - return async (context) => { + export() { + return async ( + context: RuntimeActionContext< + FunctionRuntimeParameters + >, + ) => { const action: BlockAction = context.action; const handler = this.matchHandler(action); if (handler === null) { @@ -116,7 +119,12 @@ export class ActionsRouter< } but this app has no action handler defined to handle it!`, ); } - return await handler(context); + + const enrichedContext = enrichContext(context) as ActionContext< + FunctionRuntimeParameters + >; + + return await handler(enrichedContext); }; } diff --git a/src/functions/interactivity/action_router_test.ts b/src/functions/interactivity/action_router_test.ts index 2829da8d..08bc327a 100644 --- a/src/functions/interactivity/action_router_test.ts +++ b/src/functions/interactivity/action_router_test.ts @@ -1,3 +1,4 @@ +import { SlackAPI } from "../../deps.ts"; import { assertEquals, assertExists, @@ -128,11 +129,13 @@ const SlackActionHandlerTester: SlackActionHandlerTesterFn = < trigger_id: "123", response_url: "asdf", }; + const token = args.token || "slack-function-test-token"; return { inputs, env: args.env || {}, - token: args.token || "slack-function-test-token", + token, + client: SlackAPI(token), team_id: args.team_id || "test-team-id", action: args.action || DEFAULT_ACTION, body: args.body || DEFAULT_BODY, @@ -173,6 +176,7 @@ Deno.test("ActionsRouter", async (t) => { assertExists(ctx.token); assertExists(ctx.action); assertExists(ctx.env); + assertExists(ctx.client); handlerCalled = true; }); await router(createContext({ inputs })); @@ -190,6 +194,7 @@ Deno.test("ActionsRouter action matching happy path", async (t) => { assertExists(ctx.token); assertExists(ctx.action); assertExists(ctx.env); + assertExists(ctx.client); handlerCalled = true; }); await router(createContext({ inputs })); diff --git a/src/functions/interactivity/types.ts b/src/functions/interactivity/types.ts index 279de626..17666eff 100644 --- a/src/functions/interactivity/types.ts +++ b/src/functions/interactivity/types.ts @@ -1,5 +1,6 @@ import { - FunctionContext, + BaseRuntimeFunctionContext, + FunctionContextEnrichment, FunctionDefinitionArgs, FunctionParameters, FunctionRuntimeParameters, @@ -48,10 +49,9 @@ export type UnhandledEventHandler = Definition extends } : never; -export type BaseInteractivityContext = Omit< - FunctionContext, - "event" ->; +export type BaseInteractivityContext = + & BaseRuntimeFunctionContext + & FunctionContextEnrichment; export type ActionContext = & BaseInteractivityContext @@ -154,3 +154,16 @@ export type ViewConstraintObject = { }; export type BasicConstraintField = string | string[] | RegExp; + +// -- These types represent the deno-slack-runtime function handler interfaces +export type RuntimeActionContext = + & BaseRuntimeFunctionContext + & ActionSpecificContext; + +export type RuntimeViewSubmissionContext = + & BaseRuntimeFunctionContext + & ViewSubmissionSpecificContext; + +export type RuntimeViewClosedContext = + & BaseRuntimeFunctionContext + & ViewClosedSpecificContext; diff --git a/src/functions/interactivity/view_router.ts b/src/functions/interactivity/view_router.ts index c5924f4b..47281477 100644 --- a/src/functions/interactivity/view_router.ts +++ b/src/functions/interactivity/view_router.ts @@ -4,6 +4,7 @@ import { } from "../../parameters/mod.ts"; import { SlackFunctionDefinition } from "../mod.ts"; import { UnhandledEventError } from "../unhandled-event-error.ts"; +import { enrichContext } from "../enrich-context.ts"; import { FunctionDefinitionArgs, FunctionRuntimeParameters } from "../types.ts"; import { matchBasicConstraintField, @@ -11,10 +12,10 @@ import { } from "./matchers.ts"; import type { BasicConstraintField, - ViewClosedContext, + RuntimeViewClosedContext, + RuntimeViewSubmissionContext, ViewClosedHandler, ViewConstraintObject, - ViewSubmissionContext, ViewSubmissionHandler, } from "./types.ts"; import { View, ViewEvents } from "./view_types.ts"; @@ -135,7 +136,7 @@ class ViewRouter< * Method for handling view_closed events. This should be the `viewClosed` export of your function module. */ async viewClosed( - context: ViewClosedContext< + context: RuntimeViewClosedContext< FunctionRuntimeParameters >, ) { @@ -147,15 +148,16 @@ class ViewRouter< } but this app has no view handler defined to handle it!`, ); } - return await handler(context); + const enrichedContext = enrichContext(context); + + return await handler(enrichedContext); } /** * Method for handling view_submission events. This should be the `viewSubmission` export of your function module. */ - async viewSubmission( - context: ViewSubmissionContext< + context: RuntimeViewSubmissionContext< FunctionRuntimeParameters >, ) { @@ -167,7 +169,9 @@ class ViewRouter< } but this app has no view handler defined to handle it!`, ); } - return await handler(context); + const enrichedContext = enrichContext(context); + + return await handler(enrichedContext); } private matchHandler( diff --git a/src/functions/interactivity/view_router_test.ts b/src/functions/interactivity/view_router_test.ts index 6bb5e6d5..e51a7ea0 100644 --- a/src/functions/interactivity/view_router_test.ts +++ b/src/functions/interactivity/view_router_test.ts @@ -1,3 +1,4 @@ +import { SlackAPI } from "../../deps.ts"; import { assertEquals, assertExists, @@ -196,11 +197,13 @@ const SlackViewSubmissionHandlerTester: SlackViewSubmissionHandlerTesterFn = < response_urls: [], trigger_id: "12345", }; + const token = args.token || "slack-function-test-token"; return { inputs, env: args.env || {}, - token: args.token || "slack-function-test-token", + token, + client: SlackAPI(token), view: args.view || DEFAULT_VIEW, body: args.body || DEFAULT_BODY, team_id: DEFAULT_VIEW.team_id, @@ -264,11 +267,13 @@ const SlackViewClosedHandlerTester: SlackViewClosedHandlerTesterFn = < token: "123", is_cleared: false, }; + const token = args.token || "slack-function-test-token"; return { inputs, env: args.env || {}, - token: args.token || "slack-function-test-token", + token, + client: SlackAPI(token), view: args.view || DEFAULT_VIEW, body: args.body || DEFAULT_BODY, team_id: DEFAULT_VIEW.team_id, @@ -309,6 +314,7 @@ Deno.test("ViewRouter viewSubmission", async (t) => { assertExists(ctx.inputs); assertEquals(ctx.inputs.garbage, inputs.garbage); assertExists(ctx.token); + assertExists(ctx.client); assertExists(ctx.view); assertExists< ViewSubmissionInvocationBody< @@ -336,6 +342,7 @@ Deno.test("ViewRouter viewSubmission happy path", async (t) => { assertExists(ctx.token); assertExists(ctx.view); assertExists(ctx.env); + assertExists(ctx.client); handlerCalled = true; }); await router.viewSubmission(createSubmissionContext({ inputs })); @@ -426,6 +433,7 @@ Deno.test("ViewRouter viewClosed", async (t) => { assertExists(ctx.inputs); assertEquals(ctx.inputs.garbage, inputs.garbage); assertExists(ctx.token); + assertExists(ctx.client); assertExists(ctx.view); assertExists< ViewClosedInvocationBody< @@ -451,6 +459,7 @@ Deno.test("ViewRouter viewClosed happy path", async (t) => { router.addClosedHandler(DEFAULT_VIEW.callback_id, (ctx) => { assertExists(ctx.inputs); assertExists(ctx.token); + assertExists(ctx.client); assertExists(ctx.view); assertExists(ctx.env); handlerCalled = true; diff --git a/src/functions/slack-function.ts b/src/functions/slack-function.ts index fbf00c1e..68c5d0b7 100644 --- a/src/functions/slack-function.ts +++ b/src/functions/slack-function.ts @@ -2,8 +2,14 @@ import { ParameterSetDefinition, PossibleParameterKeys, } from "../parameters/mod.ts"; -import { SlackFunctionHandler, SlackFunctionType } from "./types.ts"; +import { + EnrichedSlackFunctionHandler, + RuntimeFunctionContext, + RuntimeUnhandledEventContext, + SlackFunctionType, +} from "./types.ts"; import { SlackFunctionDefinition } from "./mod.ts"; +import { enrichContext } from "./enrich-context.ts"; import { BlockActionsRouter } from "./interactivity/action_router.ts"; import { ViewsRouter } from "./interactivity/view_router.ts"; @@ -19,13 +25,23 @@ export const SlackFunction = < RequiredInput, RequiredOutput >, - functionHandler: SlackFunctionHandler, + functionHandler: EnrichedSlackFunctionHandler, ) => { - // Start with their fn handler, and we'll wrap it up so we can append some additional functions to it + // Start with the provided fn handler, and we'll wrap it up so we can append some additional functions to it - // @ts-ignore - creating a wrapper around provided fn handler so we don't mutate it directly + // Wrap the provided handler's call so we can add additional context // deno-lint-ignore no-explicit-any - const handlerModule: any = (...args) => functionHandler(...args); + const handlerModule: any = ( + ctx: RuntimeFunctionContext, + // deno-lint-ignore no-explicit-any + ...args: any + ) => { + // enrich the context w/ additional properties + const newContext = enrichContext(ctx); + + //@ts-expect-error - intentionally specifying the provided functionHandler as the `this` arg for the handler's call + return functionHandler.apply(functionHandler, [newContext, ...args]); + }; // Unhandled events are sent to a single handler, which is not set by default handlerModule.unhandledEvent = undefined; @@ -60,7 +76,15 @@ export const SlackFunction = < // deno-lint-ignore no-explicit-any handlerModule.addUnhandledEventHandler = (handler: any) => { // Set the unhandledEvent property directly - handlerModule.unhandledEvent = handler; + handlerModule.unhandledEvent = ( + ctx: RuntimeUnhandledEventContext, + // deno-lint-ignore no-explicit-any + ...args: any + ) => { + const newContext = enrichContext(ctx); + + return handler.apply(handler, [newContext, ...args]); + }; return handlerModule; }; diff --git a/src/functions/slack-function_test.ts b/src/functions/slack-function_test.ts index aa06df38..50d3fb7d 100644 --- a/src/functions/slack-function_test.ts +++ b/src/functions/slack-function_test.ts @@ -1,4 +1,4 @@ -import { assertEquals, mock } from "../dev_deps.ts"; +import { assertEquals, assertExists, mock } from "../dev_deps.ts"; import { DefineFunction, SlackFunction } from "../mod.ts"; const TestFunction = DefineFunction({ @@ -52,15 +52,40 @@ Deno.test("SlackFunction unhandledEvent is defined after calling addUnhandledEve }); Deno.test("Main handler should pass arguments through", () => { - const mainFnHandler = mock.spy(() => ({ outputs: {} })); + const args = { test: "arguments" }; + + // deno-lint-ignore no-explicit-any + const mainFnHandler = mock.spy((ctx: any) => { + assertEquals(ctx.test, args.test); + assertExists(ctx.client); + + return { outputs: {} }; + }); const handlers = SlackFunction(TestFunction, mainFnHandler); const typedHandlers = typeHandlersForTesting(handlers); + typedHandlers(args); + + mock.assertSpyCalls(mainFnHandler, 1); +}); + +Deno.test("Main handler should have a client instance", () => { const args = { test: "arguments" }; + + // deno-lint-ignore no-explicit-any + const mainFnHandler = mock.spy((ctx: any) => { + assertExists(ctx.client); + + return { outputs: {} }; + }); + + const handlers = SlackFunction(TestFunction, mainFnHandler); + const typedHandlers = typeHandlersForTesting(handlers); + typedHandlers(args); - mock.assertSpyCallArgs(mainFnHandler, 0, [args]); + mock.assertSpyCalls(mainFnHandler, 1); }); Deno.test("addBlockActionsHandler", async () => { @@ -117,12 +142,17 @@ Deno.test("addUnhandledEventHandler", async () => { const handlers = SlackFunction(TestFunction, mainFnHandler); const typedHandlers = typeHandlersForTesting(handlers); - const handlerSpy = mock.spy(); + // deno-lint-ignore no-explicit-any + const handlerSpy = mock.spy((ctx: any) => { + assertEquals(ctx.some, args.some); + assertExists(ctx.client); + + return { outputs: {} }; + }); typedHandlers.addUnhandledEventHandler(handlerSpy); const args = { some: "arguments" }; await typedHandlers.unhandledEvent(args); - mock.assertSpyCallArgs(handlerSpy, 0, [args]); }); const typeHandlersForTesting = (handlers: HandlerType) => { diff --git a/src/functions/slack_function_handler_type_test.ts b/src/functions/slack_function_handler_type_test.ts index a9bb9dc0..c03dc000 100644 --- a/src/functions/slack_function_handler_type_test.ts +++ b/src/functions/slack_function_handler_type_test.ts @@ -2,13 +2,16 @@ import { assertEquals, assertExists } from "../dev_deps.ts"; import { assertEqualsTypedValues } from "../test_utils.ts"; import { SlackFunctionTester } from "./tester/mod.ts"; import { DefineFunction } from "./mod.ts"; -import { SlackFunctionHandler } from "./types.ts"; +import { + EnrichedSlackFunctionHandler, + RuntimeSlackFunctionHandler, +} from "./types.ts"; import { Schema } from "../mod.ts"; // These tests are to ensure our Function Handler types are supporting the use cases we want to // Any "failures" here will most likely be reflected in Type errors -Deno.test("SlackFunctionHandler with inputs and outputs", () => { +Deno.test("EnrichedSlackFunctionHandler with inputs and outputs", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -30,7 +33,7 @@ Deno.test("SlackFunctionHandler with inputs and outputs", () => { required: ["out"], }, }); - const handler: SlackFunctionHandler = ( + const handler: EnrichedSlackFunctionHandler = ( { inputs }, ) => { return { @@ -56,7 +59,7 @@ Deno.test("SlackFunctionHandler with inputs and outputs", () => { ); }); -Deno.test("SlackFunctionHandler with optional input", () => { +Deno.test("EnrichedSlackFunctionHandler with optional input", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -78,7 +81,7 @@ Deno.test("SlackFunctionHandler with optional input", () => { required: ["out"], }, }); - const handler: SlackFunctionHandler = ( + const handler: EnrichedSlackFunctionHandler = ( { inputs }, ) => { return { @@ -93,23 +96,24 @@ Deno.test("SlackFunctionHandler with optional input", () => { assertEqualsTypedValues(result.outputs?.out, "default"); }); -Deno.test("SlackFunctionHandler with no inputs or outputs", () => { +Deno.test("EnrichedSlackFunctionHandler with no inputs or outputs", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", source_file: "test.ts", }); - const handler: SlackFunctionHandler = () => { - return { - outputs: {}, + const handler: EnrichedSlackFunctionHandler = + () => { + return { + outputs: {}, + }; }; - }; const { createContext } = SlackFunctionTester(TestFn); const result = handler(createContext({ inputs: {} })); assertEqualsTypedValues(result.outputs, {}); }); -Deno.test("SlackFunctionHandler with undefined inputs and outputs", () => { +Deno.test("EnrichedSlackFunctionHandler with undefined inputs and outputs", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -117,17 +121,18 @@ Deno.test("SlackFunctionHandler with undefined inputs and outputs", () => { input_parameters: undefined, output_parameters: undefined, }); - const handler: SlackFunctionHandler = () => { - return { - outputs: {}, + const handler: EnrichedSlackFunctionHandler = + () => { + return { + outputs: {}, + }; }; - }; const { createContext } = SlackFunctionTester(TestFn); const result = handler(createContext({ inputs: {} })); assertEqualsTypedValues(result.outputs, {}); }); -Deno.test("SlackFunctionHandler with empty inputs and outputs", () => { +Deno.test("EnrichedSlackFunctionHandler with empty inputs and outputs", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -135,17 +140,18 @@ Deno.test("SlackFunctionHandler with empty inputs and outputs", () => { input_parameters: { properties: {}, required: [] }, output_parameters: { properties: {}, required: [] }, }); - const handler: SlackFunctionHandler = () => { - return { - outputs: {}, + const handler: EnrichedSlackFunctionHandler = + () => { + return { + outputs: {}, + }; }; - }; const { createContext } = SlackFunctionTester(TestFn); const result = handler(createContext({ inputs: {} })); assertEqualsTypedValues(result.outputs, {}); }); -Deno.test("SlackFunctionHandler with only inputs", () => { +Deno.test("EnrichedSlackFunctionHandler with only inputs", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -159,7 +165,7 @@ Deno.test("SlackFunctionHandler with only inputs", () => { required: ["in"], }, }); - const handler: SlackFunctionHandler = ( + const handler: EnrichedSlackFunctionHandler = ( { inputs }, ) => { const _test = inputs.in; @@ -174,7 +180,7 @@ Deno.test("SlackFunctionHandler with only inputs", () => { assertEqualsTypedValues(result.outputs, {}); }); -Deno.test("SlackFunctionHandler with only outputs", () => { +Deno.test("EnrichedSlackFunctionHandler with only outputs", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -188,19 +194,20 @@ Deno.test("SlackFunctionHandler with only outputs", () => { required: ["out"], }, }); - const handler: SlackFunctionHandler = () => { - return { - outputs: { - out: "test", - }, + const handler: EnrichedSlackFunctionHandler = + () => { + return { + outputs: { + out: "test", + }, + }; }; - }; const { createContext } = SlackFunctionTester(TestFn); const result = handler(createContext({ inputs: {} })); assertEqualsTypedValues(result.outputs?.out, "test"); }); -Deno.test("SlackFunctionHandler with input and output object", () => { +Deno.test("EnrichedSlackFunctionHandler with input and output object", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -226,7 +233,7 @@ Deno.test("SlackFunctionHandler with input and output object", () => { required: ["anObject"], }, }); - const handler: SlackFunctionHandler = ( + const handler: EnrichedSlackFunctionHandler = ( { inputs }, ) => { return { @@ -244,7 +251,7 @@ Deno.test("SlackFunctionHandler with input and output object", () => { assertEqualsTypedValues(result.outputs?.anObject.out, "test"); }); -Deno.test("SlackFunctionHandler with only completed false", () => { +Deno.test("EnrichedSlackFunctionHandler with only completed false", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -258,17 +265,18 @@ Deno.test("SlackFunctionHandler with only completed false", () => { required: ["example"], }, }); - const handler: SlackFunctionHandler = () => { - return { - completed: false, + const handler: EnrichedSlackFunctionHandler = + () => { + return { + completed: false, + }; }; - }; const { createContext } = SlackFunctionTester(TestFn); const result = handler(createContext({ inputs: {} })); assertEqualsTypedValues(result.completed, false); }); -Deno.test("SlackFunctionHandler with only error", () => { +Deno.test("EnrichedSlackFunctionHandler with only error", () => { const TestFn = DefineFunction({ callback_id: "test", title: "test fn", @@ -282,17 +290,18 @@ Deno.test("SlackFunctionHandler with only error", () => { required: ["example"], }, }); - const handler: SlackFunctionHandler = () => { - return { - error: "error", + const handler: EnrichedSlackFunctionHandler = + () => { + return { + error: "error", + }; }; - }; const { createContext } = SlackFunctionTester(TestFn); const result = handler(createContext({ inputs: {} })); assertEqualsTypedValues(result.error, "error"); }); -Deno.test("SlackFunctionHandler using Custom Types", () => { +Deno.test("EnrichedSlackFunctionHandler using Custom Types", () => { const TestFunction = DefineFunction({ callback_id: "my_callback_id", source_file: "test", @@ -327,39 +336,40 @@ Deno.test("SlackFunctionHandler using Custom Types", () => { }, }; - const handler: SlackFunctionHandler = ( - { inputs }, - ) => { - inputs.interactivity.interactor.secret; - const { interactivity, user_context } = inputs; - assertEqualsTypedValues(interactivity, sharedInputs.interactivity); - assertEqualsTypedValues( - interactivity.interactivity_pointer, - sharedInputs.interactivity.interactivity_pointer, - ); - assertEqualsTypedValues( - interactivity.interactor.id, - sharedInputs.interactivity.interactor.id, - ); - assertEqualsTypedValues( - interactivity.interactor.secret, - sharedInputs.interactivity.interactor.secret, - ); - assertEqualsTypedValues(user_context, sharedInputs.user_context); - assertEqualsTypedValues( - user_context.secret, - sharedInputs.user_context.secret, - ); - assertEqualsTypedValues(user_context.id, sharedInputs.user_context.id); - assertEqualsTypedValues( - user_context.secret, - sharedInputs.user_context.secret, - ); + const handler: EnrichedSlackFunctionHandler = + ( + { inputs }, + ) => { + inputs.interactivity.interactor.secret; + const { interactivity, user_context } = inputs; + assertEqualsTypedValues(interactivity, sharedInputs.interactivity); + assertEqualsTypedValues( + interactivity.interactivity_pointer, + sharedInputs.interactivity.interactivity_pointer, + ); + assertEqualsTypedValues( + interactivity.interactor.id, + sharedInputs.interactivity.interactor.id, + ); + assertEqualsTypedValues( + interactivity.interactor.secret, + sharedInputs.interactivity.interactor.secret, + ); + assertEqualsTypedValues(user_context, sharedInputs.user_context); + assertEqualsTypedValues( + user_context.secret, + sharedInputs.user_context.secret, + ); + assertEqualsTypedValues(user_context.id, sharedInputs.user_context.id); + assertEqualsTypedValues( + user_context.secret, + sharedInputs.user_context.secret, + ); - return { - outputs: inputs, + return { + outputs: inputs, + }; }; - }; const { createContext } = SlackFunctionTester(TestFunction); @@ -372,7 +382,7 @@ Deno.test("SlackFunctionHandler using Custom Types", () => { assertExists(result.outputs?.user_context.secret); }); -Deno.test("SlackFunctionHandler using Objects with additional properties", () => { +Deno.test("EnrichedSlackFunctionHandler using Objects with additional properties", () => { const TestFunction = DefineFunction({ callback_id: "my_callback_id", source_file: "test", @@ -405,20 +415,24 @@ Deno.test("SlackFunctionHandler using Objects with additional properties", () => addlPropertiesObj: { aString: "hi" }, }; - const handler: SlackFunctionHandler = ( - { inputs }, - ) => { - const { addlPropertiesObj } = inputs; - assertEqualsTypedValues(addlPropertiesObj, sharedInputs.addlPropertiesObj); - assertEqualsTypedValues( - addlPropertiesObj.aString, - sharedInputs.addlPropertiesObj.aString, - ); - assertEquals(addlPropertiesObj.anythingElse, undefined); - return { - outputs: inputs, + const handler: EnrichedSlackFunctionHandler = + ( + { inputs }, + ) => { + const { addlPropertiesObj } = inputs; + assertEqualsTypedValues( + addlPropertiesObj, + sharedInputs.addlPropertiesObj, + ); + assertEqualsTypedValues( + addlPropertiesObj.aString, + sharedInputs.addlPropertiesObj.aString, + ); + assertEquals(addlPropertiesObj.anythingElse, undefined); + return { + outputs: inputs, + }; }; - }; const { createContext } = SlackFunctionTester(TestFunction); @@ -429,7 +443,7 @@ Deno.test("SlackFunctionHandler using Objects with additional properties", () => assertEquals(result.outputs?.addlPropertiesObj.anythingElse, undefined); }); -Deno.test("SlackFunctionHandler using Objects without additional properties", () => { +Deno.test("EnrichedSlackFunctionHandler using Objects without additional properties", () => { const TestFunction = DefineFunction({ callback_id: "my_callback_id", source_file: "test", @@ -464,24 +478,25 @@ Deno.test("SlackFunctionHandler using Objects without additional properties", () noAddlPropertiesObj: { aString: "hi" }, }; - const handler: SlackFunctionHandler = ( - { inputs }, - ) => { - const { noAddlPropertiesObj } = inputs; - assertEqualsTypedValues( - noAddlPropertiesObj, - sharedInputs.noAddlPropertiesObj, - ); - assertEqualsTypedValues( - noAddlPropertiesObj.aString, - sharedInputs.noAddlPropertiesObj.aString, - ); - // @ts-expect-error anythingElse cant exist - assertEquals(noAddlPropertiesObj.anythingElse, undefined); - return { - outputs: inputs, + const handler: EnrichedSlackFunctionHandler = + ( + { inputs }, + ) => { + const { noAddlPropertiesObj } = inputs; + assertEqualsTypedValues( + noAddlPropertiesObj, + sharedInputs.noAddlPropertiesObj, + ); + assertEqualsTypedValues( + noAddlPropertiesObj.aString, + sharedInputs.noAddlPropertiesObj.aString, + ); + // @ts-expect-error anythingElse cant exist + assertEquals(noAddlPropertiesObj.anythingElse, undefined); + return { + outputs: inputs, + }; }; - }; const { createContext } = SlackFunctionTester(TestFunction); @@ -493,3 +508,32 @@ Deno.test("SlackFunctionHandler using Objects without additional properties", () // @ts-expect-error anythingElse cant exist assertEquals(result.outputs?.noAddlPropertiesObj.anythingElse, undefined); }); + +Deno.test("RuntimeSlackFunctionHandler type should not include a client property", () => { + const TestFn = DefineFunction({ + callback_id: "test", + title: "test fn", + source_file: "test.ts", + output_parameters: { + properties: { + example: { + type: "boolean", + }, + }, + required: ["example"], + }, + }); + const handler: RuntimeSlackFunctionHandler = ( + ctx, + ) => { + // @ts-expect-error ctx.client shouldn't be typed by RuntimeSlackFunctionHandler type - but SlackFunctionTester should inject it at runtime + assertExists(ctx.client); + + return { + completed: false, + }; + }; + const { createContext } = SlackFunctionTester(TestFn); + const result = handler(createContext({ inputs: {} })); + assertEqualsTypedValues(result.completed, false); +}); diff --git a/src/functions/tester/mod.ts b/src/functions/tester/mod.ts index ac42ecc7..06f04e8a 100644 --- a/src/functions/tester/mod.ts +++ b/src/functions/tester/mod.ts @@ -1,3 +1,4 @@ +import { SlackAPI } from "../../deps.ts"; import { ParameterSetDefinition, PossibleParameterKeys, @@ -39,6 +40,7 @@ export const SlackFunctionTester: SlackFunctionTesterFn = < args, ) => { const ts = new Date(); + const token = args.token || "slack-function-test-token"; return { inputs: (args.inputs || {}) as FunctionRuntimeParameters< @@ -46,7 +48,8 @@ export const SlackFunctionTester: SlackFunctionTesterFn = < RequiredInput >, env: args.env || {}, - token: args.token || "slack-function-test-token", + token, + client: SlackAPI(token), team_id: args.team_id || "test-team-id", event: args.event || { type: "function_executed", diff --git a/src/functions/types.ts b/src/functions/types.ts index 6bb9d69d..8d3b3f77 100644 --- a/src/functions/types.ts +++ b/src/functions/types.ts @@ -1,3 +1,4 @@ +import { SlackAPIClient } from "../deps.ts"; import { Env } from "../types.ts"; import { ManifestFunctionSchema } from "../manifest/manifest_schema.ts"; import { @@ -138,24 +139,32 @@ export type FunctionRuntimeParameters< >; }; -type AsyncFunctionHandler = { +type AsyncFunctionHandler< + InputParameters, + OutputParameters, + Context extends BaseRuntimeFunctionContext, +> = { ( - context: FunctionContext, + context: Context, ): Promise>; }; -type SyncFunctionHandler = { +type SyncFunctionHandler< + InputParameters, + OutputParameters, + Context extends BaseRuntimeFunctionContext, +> = { ( - context: FunctionContext, + context: Context, ): FunctionHandlerReturnArgs; }; /** * @description Slack Function handler from a function definition */ -export type SlackFunctionHandler = Definition extends +export type RuntimeSlackFunctionHandler = Definition extends FunctionDefinitionArgs - ? BaseSlackFunctionHandler< + ? BaseRuntimeSlackFunctionHandler< FunctionRuntimeParameters, FunctionRuntimeParameters > @@ -164,12 +173,53 @@ export type SlackFunctionHandler = Definition extends /** * @description Slack Function handler from input and output types directly */ -export type BaseSlackFunctionHandler< +export type BaseRuntimeSlackFunctionHandler< + InputParameters extends FunctionParameters, + OutputParameters extends FunctionParameters, +> = + | AsyncFunctionHandler< + InputParameters, + OutputParameters, + RuntimeFunctionContext + > + | SyncFunctionHandler< + InputParameters, + OutputParameters, + RuntimeFunctionContext + >; + +/** + * @description Slack Function handler from a function definition + */ +export type EnrichedSlackFunctionHandler = Definition extends + FunctionDefinitionArgs + ? (BaseEnrichedSlackFunctionHandler< + FunctionRuntimeParameters, + FunctionRuntimeParameters + >) + : never; + +/** + * @description Slack Function handler from input and output types directly + */ +type BaseEnrichedSlackFunctionHandler< InputParameters extends FunctionParameters, OutputParameters extends FunctionParameters, > = - | AsyncFunctionHandler - | SyncFunctionHandler; + | AsyncFunctionHandler< + InputParameters, + OutputParameters, + FunctionContext + > + | SyncFunctionHandler< + InputParameters, + OutputParameters, + FunctionContext + >; + +// export type SlackFunctionHandler = EnrichedSlackFunctionHandler< +// Definition +// >; type SuccessfulFunctionReturnArgs< OutputParameters extends FunctionParameters, @@ -198,7 +248,9 @@ export type FunctionHandlerReturnArgs< | ErroredFunctionReturnArgs | PendingFunctionReturnArgs; -export type FunctionContext< +// This describes the base-version of context objects deno-slack-runtime passes into different function handlers (i.e. main fn handler, blockActions, etc). +// Each function handler type extends this with it's own specific additions. +export type BaseRuntimeFunctionContext< InputParameters extends FunctionParameters, > = { /** @@ -217,9 +269,25 @@ export type FunctionContext< * @description A unique encoded ID representing the Slack team associated with the workspace where the function execution takes place. */ team_id: string; - event: FunctionInvocationBody["event"]; }; +// SDK Function handlers receive these additional properties on the function context object +export type FunctionContextEnrichment = { + client: SlackAPIClient; +}; + +// This is the context deno-slack-runtime passes to the main function handler +export type RuntimeFunctionContext = + & BaseRuntimeFunctionContext + & { + event: FunctionInvocationBody["event"]; + }; + +// This is the enriched context object passed into the main function handler setup with SlackFunction() +export type FunctionContext< + InputParameters extends FunctionParameters, +> = RuntimeFunctionContext & FunctionContextEnrichment; + // Allow undefined here for functions that have no inputs and/or outputs export type FunctionParameters = { // deno-lint-ignore no-explicit-any @@ -269,7 +337,7 @@ export type FunctionDefinitionArgs< export type SlackFunctionType = Definition extends FunctionDefinitionArgs ? ( - & SlackFunctionHandler + & EnrichedSlackFunctionHandler & { addBlockActionsHandler( actionConstraint: BlockActionConstraint, @@ -297,3 +365,13 @@ export type SlackFunctionType = Definition extends } ) : never; + +// This is the context deno-slack-runtime passes to the unhandledEvent handler +export type RuntimeUnhandledEventContext< + InputParameters extends FunctionParameters, +> = + & BaseRuntimeFunctionContext + & { + // deno-lint-ignore no-explicit-any + body: any; + }; diff --git a/src/mod.ts b/src/mod.ts index b06374bc..a6cc0af2 100644 --- a/src/mod.ts +++ b/src/mod.ts @@ -19,3 +19,4 @@ export { DefineOAuth2Provider } from "./providers/oauth2/mod.ts"; export { default as Schema } from "./schema/mod.ts"; export { DefineDatastore } from "./datastore/mod.ts"; export { SlackFunctionTester } from "./functions/tester/mod.ts"; +export { SlackAPI } from "./deps.ts"; diff --git a/src/types.ts b/src/types.ts index c5bc5905..a4a8f71b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,7 +1,13 @@ export type { - BaseSlackFunctionHandler, + /** + * @deprecated Use SlackFunction() to define handlers instead + */ + BaseRuntimeSlackFunctionHandler as BaseSlackFunctionHandler, BlockActionHandler, - SlackFunctionHandler, + /** + * @deprecated Use SlackFunction() to define handlers instead + */ + RuntimeSlackFunctionHandler as SlackFunctionHandler, } from "./functions/types.ts"; // ---------------------------------------------------------------------------- @@ -23,3 +29,5 @@ export type InvocationPayload = { // Env // ---------------------------------------------------------------------------- export type Env = Record; + +export type { SlackAPIClient, Trigger } from "./deps.ts";