Full Code of slackapi/deno-slack-sdk for AI

main a27bd9ec642d cached
194 files
626.6 KB
149.8k tokens
363 symbols
1 requests
Download .txt
Showing preview only (680K chars total). Download the full file or copy to clipboard to get everything.
Repository: slackapi/deno-slack-sdk
Branch: main
Commit: a27bd9ec642d
Files: 194
Total size: 626.6 KB

Directory structure:
gitextract_v0ijlb09/

├── .github/
│   ├── CODEOWNERS
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.md
│   │   ├── feature.md
│   │   └── question.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   ├── maintainers_guide.md
│   └── workflows/
│       ├── deno.yml
│       ├── dependencies.yml
│       ├── e2e.yml
│       ├── npm-publish.yml
│       ├── npm.yml
│       ├── publish.yml
│       └── samples.yml
├── .gitignore
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── deno.jsonc
├── docs/
│   ├── datastores.md
│   ├── events.md
│   ├── functions-action-handlers.md
│   ├── functions-suggestion-handlers.md
│   ├── functions-view-handlers.md
│   ├── functions.md
│   ├── manifest.md
│   ├── types.md
│   └── workflows.md
├── scripts/
│   ├── build_npm.ts
│   ├── bundle.ts
│   └── imports/
│       └── update.ts
├── src/
│   ├── README.md
│   ├── datastore/
│   │   ├── datastore_test.ts
│   │   ├── mod.ts
│   │   └── types.ts
│   ├── deps.ts
│   ├── dev_deps.ts
│   ├── events/
│   │   ├── events_test.ts
│   │   ├── mod.ts
│   │   └── types.ts
│   ├── functions/
│   │   ├── definitions/
│   │   │   ├── connector-function.ts
│   │   │   ├── connector-function_test.ts
│   │   │   ├── mod.ts
│   │   │   ├── slack-function.ts
│   │   │   └── slack-function_test.ts
│   │   ├── enrich-context.ts
│   │   ├── enrich-context_test.ts
│   │   ├── interactivity/
│   │   │   ├── block_actions_router.ts
│   │   │   ├── block_actions_router_test.ts
│   │   │   ├── block_actions_types.ts
│   │   │   ├── block_kit_types.ts
│   │   │   ├── block_suggestion_router.ts
│   │   │   ├── block_suggestion_router_test.ts
│   │   │   ├── block_suggestion_types.ts
│   │   │   ├── matchers.ts
│   │   │   ├── mod.ts
│   │   │   ├── types.ts
│   │   │   ├── view_router.ts
│   │   │   ├── view_router_test.ts
│   │   │   └── view_types.ts
│   │   ├── mod.ts
│   │   ├── slack-function.ts
│   │   ├── slack-function_test.ts
│   │   ├── tester/
│   │   │   ├── function_tester_test.ts
│   │   │   ├── mod.ts
│   │   │   └── types.ts
│   │   ├── types.ts
│   │   ├── types_base_runtime_function_handler_test.ts
│   │   ├── types_runtime_slack_function_handler_test.ts
│   │   └── unhandled-event-error.ts
│   ├── manifest/
│   │   ├── errors.ts
│   │   ├── errors_test.ts
│   │   ├── manifest_schema.ts
│   │   ├── manifest_test.ts
│   │   ├── mod.ts
│   │   ├── types.ts
│   │   └── types_util.ts
│   ├── mod.ts
│   ├── mod_test.ts
│   ├── parameters/
│   │   ├── define_property.ts
│   │   ├── define_property_test.ts
│   │   ├── definition_types.ts
│   │   ├── mod.ts
│   │   ├── param.ts
│   │   ├── param_test.ts
│   │   ├── parameter-variable_test.ts
│   │   ├── types.ts
│   │   ├── with-untyped-object-proxy.ts
│   │   └── with-untyped-object-proxy_test.ts
│   ├── providers/
│   │   └── oauth2/
│   │       ├── mod.ts
│   │       ├── oauth2_test.ts
│   │       └── types.ts
│   ├── schema/
│   │   ├── mod.ts
│   │   ├── providers/
│   │   │   ├── mod.ts
│   │   │   └── oauth2/
│   │   │       ├── mod.ts
│   │   │       └── types.ts
│   │   ├── schema_types.ts
│   │   ├── slack/
│   │   │   ├── functions/
│   │   │   │   ├── _scripts/
│   │   │   │   │   ├── .gitignore
│   │   │   │   │   ├── README.md
│   │   │   │   │   ├── generate
│   │   │   │   │   └── src/
│   │   │   │   │       ├── templates/
│   │   │   │   │       │   ├── mod.ts
│   │   │   │   │       │   ├── template_function.ts
│   │   │   │   │       │   ├── template_mod.ts
│   │   │   │   │       │   ├── test_template.ts
│   │   │   │   │       │   ├── types.ts
│   │   │   │   │       │   ├── utils.ts
│   │   │   │   │       │   └── utils_test.ts
│   │   │   │   │       ├── test/
│   │   │   │   │       │   └── data/
│   │   │   │   │       │       └── function.json
│   │   │   │   │       ├── types.ts
│   │   │   │   │       ├── utils.ts
│   │   │   │   │       ├── utils_test.ts
│   │   │   │   │       └── write_function_files.ts
│   │   │   │   ├── add_bookmark.ts
│   │   │   │   ├── add_bookmark_test.ts
│   │   │   │   ├── add_pin.ts
│   │   │   │   ├── add_pin_test.ts
│   │   │   │   ├── add_reaction.ts
│   │   │   │   ├── add_reaction_test.ts
│   │   │   │   ├── add_user_to_usergroup.ts
│   │   │   │   ├── add_user_to_usergroup_test.ts
│   │   │   │   ├── archive_channel.ts
│   │   │   │   ├── archive_channel_test.ts
│   │   │   │   ├── canvas_copy.ts
│   │   │   │   ├── canvas_copy_test.ts
│   │   │   │   ├── canvas_create.ts
│   │   │   │   ├── canvas_create_test.ts
│   │   │   │   ├── canvas_update_content.ts
│   │   │   │   ├── canvas_update_content_test.ts
│   │   │   │   ├── channel_canvas_create.ts
│   │   │   │   ├── channel_canvas_create_test.ts
│   │   │   │   ├── create_channel.ts
│   │   │   │   ├── create_channel_test.ts
│   │   │   │   ├── create_usergroup.ts
│   │   │   │   ├── create_usergroup_test.ts
│   │   │   │   ├── delay.ts
│   │   │   │   ├── delay_test.ts
│   │   │   │   ├── invite_user_to_channel.ts
│   │   │   │   ├── invite_user_to_channel_test.ts
│   │   │   │   ├── mod.ts
│   │   │   │   ├── open_form.ts
│   │   │   │   ├── open_form_test.ts
│   │   │   │   ├── remove_reaction.ts
│   │   │   │   ├── remove_reaction_test.ts
│   │   │   │   ├── remove_user_from_usergroup.ts
│   │   │   │   ├── remove_user_from_usergroup_test.ts
│   │   │   │   ├── reply_in_thread.ts
│   │   │   │   ├── reply_in_thread_test.ts
│   │   │   │   ├── send_dm.ts
│   │   │   │   ├── send_dm_test.ts
│   │   │   │   ├── send_ephemeral_message.ts
│   │   │   │   ├── send_ephemeral_message_test.ts
│   │   │   │   ├── send_message.ts
│   │   │   │   ├── send_message_test.ts
│   │   │   │   ├── share_canvas.ts
│   │   │   │   ├── share_canvas_in_thread.ts
│   │   │   │   ├── share_canvas_in_thread_test.ts
│   │   │   │   ├── share_canvas_test.ts
│   │   │   │   ├── update_channel_topic.ts
│   │   │   │   └── update_channel_topic_test.ts
│   │   │   ├── mod.ts
│   │   │   ├── schema_types.ts
│   │   │   └── types/
│   │   │       ├── custom/
│   │   │       │   ├── custom_slack_types_test.ts
│   │   │       │   ├── form_input.ts
│   │   │       │   ├── interactivity.ts
│   │   │       │   ├── message_context.ts
│   │   │       │   ├── mod.ts
│   │   │       │   └── user_context.ts
│   │   │       └── mod.ts
│   │   └── types.ts
│   ├── test_utils.ts
│   ├── test_utils_test.ts
│   ├── type_utils.ts
│   ├── types/
│   │   ├── mod.ts
│   │   ├── types.ts
│   │   └── types_test.ts
│   ├── types.ts
│   └── workflows/
│       ├── mod.ts
│       ├── types.ts
│       └── workflow-step.ts
└── tests/
    └── integration/
        ├── functions/
        │   └── runtime_context/
        │       ├── array_parameters_test.ts
        │       ├── custom_type_parameters_test.ts
        │       ├── empty_undefined_parameters_test.ts
        │       ├── incomplete_error_status_test.ts
        │       ├── input_parameter_optionality_test.ts
        │       ├── input_parameters_test.ts
        │       ├── output_parameter_optionality_test.ts
        │       ├── output_parameters_test.ts
        │       ├── typed_object_property_test.ts
        │       └── untyped_object_property_test.ts
        ├── parameters/
        │   ├── parameter_variable_test.ts
        │   └── parameter_variable_unwrapped_test.ts
        ├── schema/
        │   └── slack/
        │       └── functions/
        │           └── _scripts/
        │               └── write_function_files_test.ts
        └── workflows/
            └── workflows_test.ts

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/CODEOWNERS
================================================
# Salesforce Open Source project configuration
# Learn more: https://github.com/salesforce/oss-template
#ECCN:Open Source
#GUSINFO:Open Source,Open Source Workflow

# @slackapi/denosaurs
# are code reviewers for all changes in this repo.
* @slackapi/denosaurs

# @slackapi/developer-education
# are code reviewers for changes in the `/docs` directory.
/docs/ @slackapi/developer-education


================================================
FILE: .github/CONTRIBUTING.md
================================================
# Contributors Guide

Interested in contributing? Awesome! Before you do though, please read our
[Code of Conduct](https://slackhq.github.io/code-of-conduct). We take it very
seriously, and expect that you will as well.

There are many ways you can contribute! :heart:

## :bug: Bug Reports and Fixes

- If you find a bug, please search for it in the
  [Issues](https://github.com/slackapi/deno-slack-sdk/issues), and if it isn't
  already tracked,
  [create a new Bug Report Issue](https://github.com/slackapi/deno-slack-sdk/issues/new/choose).
  Fill out the "Bug Report" section of the issue template. Even if an Issue is
  closed, feel free to comment and add details, it will still be reviewed.
- Issues that have already been identified as a bug (note: able to reproduce)
  will be labelled `bug`.
- If you'd like to submit a fix for a bug,
  [send a Pull Request](#creating-a-pull-request) and mention the Issue number.
- Include tests that isolate the bug and verifies that it was fixed.

## :bulb: New Features

- If you'd like to add new functionality to this project, describe the problem
  you want to solve in a
  [new Feature Request Issue](https://github.com/slackapi/deno-slack-sdk/issues/new/choose).
- Issues that have been identified as a feature request will be labelled
  `enhancement`.
- If you'd like to implement the new feature, please wait for feedback from the
  project maintainers before spending too much time writing the code. In some
  cases, `enhancement`s may not align well with the project objectives at the
  time.

## :mag: Tests, :books: Documentation,:sparkles: Miscellaneous

- If you'd like to improve the tests, you want to make the documentation
  clearer, you have an alternative implementation of something that may have
  advantages over the way its currently done, or you have any other change, we
  would be happy to hear about it!
- If its a trivial change, go ahead and
  [send a Pull Request](#creating-a-pull-request) with the changes you have in
  mind.
- If not, [open an Issue](https://github.com/slackapi/deno-slack-sdk/issues/new)
  to discuss the idea first.

If you're new to our project and looking for some way to make your first
contribution, look for Issues labelled `good first contribution`.

## Requirements

For your contribution to be accepted:

- [x] You must have signed the
      [Contributor License Agreement (CLA)](https://cla.salesforce.com/sign-cla).
- [x] The test suite must be complete and pass.
- [x] The changes must be approved by code review.
- [x] Commits should be atomic and messages must be descriptive. Related issues
      should be mentioned by Issue number.

If the contribution doesn't meet the above criteria, you may fail our automated
checks or a maintainer will discuss it with you. You can continue to improve a
Pull Request by adding commits to the branch from which the PR was created.

[Interested in knowing more about about pull requests at Slack?](https://slack.engineering/on-empathy-pull-requests-979e4257d158#.awxtvmb2z)

## Creating a Pull Request

1. :fork_and_knife: Fork the repository on GitHub.
2. :runner: Clone/fetch your fork to your local development machine. It's a good
   idea to run the tests just to make sure everything is in order.
3. :herb: Create a new branch and check it out.
4. :crystal_ball: Make your changes and commit them locally. Magic happens here!
5. :arrow_heading_up: Push your new branch to your fork. (e.g.
   `git push username fix-issue-16`).
6. :inbox_tray: Open a Pull Request on github.com from your new branch on your
   fork to `main` in this repository.

## Maintainers

There are more details about processes and workflow in the
[Maintainer's Guide](./maintainers_guide.md).


================================================
FILE: .github/ISSUE_TEMPLATE/bug.md
================================================
---
name: Bug Report
about: Report a bug encountered while using this project
title: '[BUG] <title>'
---

<!-- If you find a bug, please search for it in the [Issues](https://github.com/slackapi/deno-slack-sdk/issues), and if it isn't already tracked then create a new issue -->

**The `deno-slack` versions**

<!-- Paste the output of `cat import_map.json | grep deno-slack` -->

**Deno runtime version**

<!-- Paste the output of `deno --version` -->

**OS info**

<!-- Paste the output of `sw_vers && uname -v` on macOS/Linux or `ver` on Windows OS -->

**Describe the bug**

<!-- A clear and concise description of what the bug is. -->

**Steps to reproduce**

<!-- Share the commands to run, source code, and project settings -->
1. 
2. 
3. 

**Expected result**

<!-- Tell what you expected to happen -->

**Actual result**

<!-- Tell what actually happened with logs, screenshots -->

**Requirements**

Please read the [Contributing guidelines](https://github.com/slackapi/deno-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules.


================================================
FILE: .github/ISSUE_TEMPLATE/feature.md
================================================
---
name: Feature request
about: Suggest a new feature for this project
title: '[FEATURE] <title>'
---

<!-- If you have a feature request, please search for it in the [Issues](https://github.com/slackapi/deno-slack-sdk/issues), and if it isn't already tracked then create a new issue -->

**Description of the problem being solved**

<!-- Please describe the problem you want to solve -->

**Alternative solutions**

<!-- Please describe the solutions you've considered -->

**Requirements**

Please read the [Contributing guidelines](https://github.com/slackapi/deno-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules.


================================================
FILE: .github/ISSUE_TEMPLATE/question.md
================================================
---
name: Question
about: Ask a question about this project
title: '[QUERY] <title>'
label: question
---

<!-- If you have a question, please search for it in the [Issues](https://github.com/slackapi/deno-slack-sdk/issues), and if it isn't already tracked then create a new issue -->

**Question**

<!-- A clear and concise question with steps to reproduce -->

**Context**

<!-- Any additional context to your question -->

**Environment**

<!-- Paste the output of `cat import_map.json | grep deno-slack` -->
<!-- Paste the output of `deno --version` -->
<!-- Paste the output of `sw_vers && uname -v` on macOS/Linux or `ver` on Windows OS -->

**Requirements**

Please read the [Contributing guidelines](https://github.com/slackapi/deno-slack-sdk/blob/main/.github/contributing.md) and [Code of Conduct](https://slackhq.github.io/code-of-conduct) before creating this issue or pull request. By submitting, you are agreeing to those rules.


================================================
FILE: .github/PULL_REQUEST_TEMPLATE.md
================================================
<!--
Thanks for sending a pull request!

Friendly reminder: this project is open sourced, the internet can see it,
make sure all the info/links shared in this pull request are public information.
-->

### Summary

<!-- A high level description of the change that will make it easier for your reviewer to make sense of the changes -->

### Testing

<!-- Describe what steps a reviewer should follow to test your changes. -->

### Special notes

<!-- Any special notes reviewers should be aware of. -->

### Requirements <!-- place an `x` in each `[ ]` -->

* [ ] I've read and understood the [Contributing Guidelines](https://github.com/slackapi/deno-slack-sdk/blob/main/.github/CONTRIBUTING.md) and have done my best effort to follow them.
* [ ] I've read and agree to the [Code of Conduct](https://slackhq.github.io/code-of-conduct).
* [ ] I've ran `deno task test` after making the changes.


================================================
FILE: .github/dependabot.yml
================================================
# Please see the documentation for all configuration options:
# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "monthly"


================================================
FILE: .github/maintainers_guide.md
================================================
# Maintainers Guide

This document describes tools, tasks and workflow that one needs to be familiar
with in order to effectively maintain this project. If you use this package
within your own software as is but don't plan on modifying it, this guide is
**not** for you.

## Tools

All you need to work on this project is a recent version of
[Deno](https://deno.land/)

<details>
  <summary>Note</summary>

- You can set up shell completion by following the
  [Shell Completion](https://deno.land/manual/getting_started/setup_your_environment#shell-completions)
  guidelines.

</details>

## Tasks

### Testing with Deno

In-code tests can be run directly with Deno:

```zsh
deno task test
```

You can also run a test coverage report with:

```zsh
deno task coverage
```

### Testing with a sample app

Sometimes you may need to test out changes in this SDK with a sample app or
project.

A modified SDK version can be used by updating the `deno-slack-sdk` import url
in the app's `import_map.json` file.

> After making changes to your imports, you may need to
> [reload your modules](https://deno.land/manual@v1.29.1/basics/modules/reloading_modules)
> in case they've been cached.

#### Using local changes

To use your own code as the SDK, change the import url to the `src/` directory
of your local `deno-slack-sdk` repo:

```json
{
  "imports": {
    "deno-slack-sdk/": "../../tools/deno-slack-sdk/src/",
    "deno-slack-api/": "jsr:@slack/api@2.9.0/"
  }
}
```

#### With remote changes

To test with unreleased changes on a remote repo, commit your intended history
to a remote branch and note the full commit SHA. (e.g.
`fc0a0a1f0722e28fecb7782513d045522d7c0d6f`).

Then in your sample app's `import_map.json` file, replace the `deno-slack-sdk`
import url with:

```json
{
  "imports": {
    "deno-slack-sdk/": "https://raw.githubusercontent.com/slackapi/deno-slack-sdk/<commit-SHA-goes-here>/src/",
    "deno-slack-api/": "jsr:@slack/api@2.9.0/"
  }
}
```

### Lint and format

The linting and formatting rules are defined in the `deno.jsonc` file, your IDE
can be set up to follow these rules:

1. Refer to the
   [Deno Set Up Your Environment](https://deno.land/manual/getting_started/setup_your_environment)
   guidelines to set up your IDE with the proper plugin.
2. Ensure that the `deno.jsonc` file is set as the configuration file for your
   IDE plugin
   - If you are using VS code
     [this](https://deno.land/manual/references/vscode_deno#using-a-configuration-file)
     is already configured in `.vscode/settings.json`

#### Linting

The list of linting rules can be found in
[the linting deno docs](https://lint.deno.land/). Currently we apply all
recommended rules.

#### Format

The list of format options is defined in the `deno.jsonc` file. They closely
resemble the default values.

### Releasing

Releases for this library are automatically generated off of git tags. Before
creating a new release, ensure that everything on the `main` branch since the
last tag is in a releasable state! At a minimum,
[run the tests](#testing-with-deno) and validate that the package meets JSR
publishing requirements by doing a dry run of the publish command:

```zsh
deno task publish:dry-run
```

To create a new release:

1. Determine the new release version. You can start off by incrementing the
   version to reflect a patch (i.e. 1.0.0 -> 1.0.1).
   - Review the pull request labels of the changes since the last release (i.e.
     `semver:minor`, `semver:patch`, `semver:major`). Tip: Your release version
     should be based on the tag of the largest change, so if the changes include
     a `semver:minor`, the release version should be upgraded to reflect a
     minor.
   - Ensure that this version adheres to [semantic versioning][semver]. See
     [Versioning](#versioning-and-tags) for correct version format. Version tags
     should match the following pattern: `1.0.1` (no `v` preceding the number).
2. Create a new branch from `main` named after the release version (e.g.
   `1.0.1`).
3. Bump the `version` field in `deno.jsonc` to the new release version.
4. Open a pull request from the version branch into `main` and get it
   approved/merged.
   1. `git commit -m 'chore(release): version 1.0.1'`
   2. `git push -u origin 1.0.1`
5. Create a new GitHub Release from the
   [Releases page](https://github.com/slackapi/deno-slack-sdk/releases) by
   clicking the "Draft a new release" button.
6. Input a new version manually into the "Choose a tag" input.
   - After you input the new version, click the "Create a new tag: x.x.x on
     publish" button. This won't create your tag immediately.
   - Auto-generate the release notes by clicking the "Auto-generate release
     notes" button. This will pull in changes that will be included in your
     release.
   - Edit the resulting notes to ensure they have decent messaging that are
     understandable by non-contributors, but each commit should still have it's
     own line.
7. Set the "Target" input to the "main" branch.
8. Name the release title after the version tag.
9. Make any adjustments to generated release notes to make sure they are
   accessible and approachable and that an end-user with little context about
   this project could still understand.
10. Publish the release by clicking the "Publish release" button!
11. After a few minutes, the corresponding version will be available on
    <https://deno.land/x/deno_slack_sdk> and <https://jsr.io/@slack/sdk>.

## Workflow

### Versioning and Tags

This project is versioned using [Semantic Versioning][semver].

### Branches

> Describe any specific branching workflow. For example: `main` is where active
> development occurs. Long running branches named feature branches are
> occasionally created for collaboration on a feature that has a large scope
> (because everyone cannot push commits to another person's open Pull Request)

### Issue Management

Labels are used to run issues through an organized workflow. Here are the basic
definitions:

- `bug`: A confirmed bug report. A bug is considered confirmed when reproduction
  steps have been documented and the issue has been reproduced.
- `enhancement`: A feature request for something this package might not already
  do.
- `docs`: An issue that is purely about documentation work.
- `tests`: An issue that is purely about testing work.
- `needs feedback`: An issue that may have claimed to be a bug but was not
  reproducible, or was otherwise missing some information.
- `discussion`: An issue that is purely meant to hold a discussion. Typically
  the maintainers are looking for feedback in this issues.
- `question`: An issue that is like a support request because the user's usage
  was not correct.
- `semver:major|minor|patch`: Metadata about how resolving this issue would
  affect the version number.
- `security`: An issue that has special consideration for security reasons.
- `good first contribution`: An issue that has a well-defined relatively-small
  scope, with clear expectations. It helps when the testing approach is also
  known.
- `duplicate`: An issue that is functionally the same as another issue. Apply
  this only if you've linked the other issue by number.

**Triage** is the process of taking new issues that aren't yet "seen" and
marking them with a basic level of information with labels. An issue should have
**one** of the following labels applied: `bug`, `enhancement`, `question`,
`needs feedback`, `docs`, `tests`, or `discussion`.

Issues are closed when a resolution has been reached. If for any reason a closed
issue seems relevant once again, reopening is great and better than creating a
duplicate issue.

## Dependency Graph

<!-- https://mermaid.js.org/syntax/flowchart.html -->
<!-- Link in mermaid are not supported on github https://github.com/mermaid-js/mermaid/issues/3077 -->

```mermaid
flowchart TD
    samples --> deno-slack-sdk
    samples --> deno-slack-api
    samples -- start hook --> deno-slack-runtime
    samples --> deno-slack-hooks
    deno-slack-hooks -. start hook .-> deno-slack-runtime
    deno-slack-sdk --> deno-slack-api
    deno-slack-hooks --> deno-slack-protocols
    deno-slack-runtime --> deno-slack-protocols
```

|                                  Links                                   |
| :----------------------------------------------------------------------: |
|       [samples](https://github.com/slack-samples/deno-hello-world)       |
|       [deno-slack-sdk](https://github.com/slackapi/deno-slack-sdk)       |
|       [deno-slack-api](https://github.com/slackapi/deno-slack-api)       |
|   [deno-slack-runtime](https://github.com/slackapi/deno-slack-runtime)   |
|     [deno-slack-hooks](https://github.com/slackapi/deno-slack-hooks)     |
| [deno-slack-protocols](https://github.com/slackapi/deno-slack-protocols) |

## Everything else

When in doubt, find the other maintainers and ask.

[semver]: http://semver.org/


================================================
FILE: .github/workflows/deno.yml
================================================
name: Deno

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  deno:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        deno-version:
          - v1.x
          - v2.x
    permissions:
      contents: read
    steps:
      - name: Setup repo
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup Deno ${{ matrix.deno-version }}
        uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
        with:
          deno-version: ${{ matrix.deno-version }}

      - name: Run tests
        run: deno task test

      - name: Generate CodeCov-friendly coverage report
        run: deno task coverage

      - name: Upload coverage to CodeCov
        uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
        if: matrix.deno-version == 'v2.x'
        with:
          files: ./lcov.info
          token: ${{ secrets.CODECOV_TOKEN }}


================================================
FILE: .github/workflows/dependencies.yml
================================================
name: Merge updates to dependencies
on:
  pull_request:
jobs:
  dependabot:
    name: "@dependabot"
    if: github.event.pull_request.user.login == 'dependabot[bot]'
    runs-on: ubuntu-latest
    permissions:
      contents: write
      pull-requests: write
    steps:
      - name: Collect metadata
        id: metadata
        uses: dependabot/fetch-metadata@25dd0e34f4fe68f24cc83900b1fe3fe149efef98 # v3.1.0
        with:
          github-token: "${{ secrets.GITHUB_TOKEN }}"
      - name: Approve
        if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor'
        run: gh pr review --approve "$PR_URL"
        env:
          PR_URL: ${{github.event.pull_request.html_url}}
          GH_TOKEN: ${{secrets.GITHUB_TOKEN}}
      - name: Automerge
        if: steps.metadata.outputs.update-type == 'version-update:semver-patch' || steps.metadata.outputs.update-type == 'version-update:semver-minor'
        run: gh pr merge --auto --squash "$PR_URL"
        env:
          PR_URL: ${{ github.event.pull_request.html_url }}
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .github/workflows/e2e.yml
================================================
# This workflow invokes and waits for the result of Slack's private E2E CI system
name: Internal E2E CI

on:
  push:
    branches:
      - main
  pull_request:

jobs:
  e2e:
    runs-on: ubuntu-latest
    permissions:
      contents: read
    steps:
      - name: Checkout the sdk
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - name: Set environment variables
        run: |
          # Short name for current branch. For PRs, use source branch (GITHUB_HEAD_REF)
          GIT_BRANCH=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
          echo "Identified deno-slack-sdk branch name: ${GIT_BRANCH}; will invoke CI system to test this branch.";
          echo "GIT_BRANCH=$GIT_BRANCH" >> $GITHUB_ENV
      - name: Kick off platform-devpx-test pipeline
        env:
          CCI_PAT: ${{ secrets.CCHEN_CIRCLECI_PERSONAL_TOKEN }}
        run: |
          IMPORT_URL="https://raw.githubusercontent.com/slackapi/deno-slack-sdk/refs/heads/${GIT_BRANCH}/src/";
          echo "Import URL: ${IMPORT_URL}";
          # https://app.circleci.com/settings/organization/github/slackapi/contexts
          TEST_PAYLOAD=$(curl --location --request POST 'https://circleci.com/api/v2/project/gh/slackapi/platform-devxp-test/pipeline' \
          --header 'Content-Type: application/json' \
          -u "${CCI_PAT}:" \
          --data "{\"branch\":\"main\",\"parameters\":{\"deno_sdk_import_url\":\"${IMPORT_URL}\"}}")
          echo $TEST_PAYLOAD;
          TEST_JOB_WORKFLOW_ID=$(echo $TEST_PAYLOAD | jq --raw-output '.id');
          if [ $TEST_JOB_WORKFLOW_ID = "null" ]; then
            echo "Problem extracting job ID from invocation API call! Aborting!";
            exit 1;
          fi
          echo "e2e test workflow started with id: $TEST_JOB_WORKFLOW_ID"
          echo "TEST_JOB_WORKFLOW_ID=${TEST_JOB_WORKFLOW_ID}" >> $GITHUB_ENV
      - name: Wait for platform-devxp-test E2E run to complete
        env:
          CCI_PAT: ${{ secrets.CCHEN_CIRCLECI_PERSONAL_TOKEN }}
        run: |
          E2E_RESULT="{}"
          E2E_STATUS="running"
          # possible status values: success, running, not_run, failed, error, failing, on_hold, canceled, unauthorized
          while [[ $E2E_STATUS != "failed" && $E2E_STATUS != "canceled" && $E2E_STATUS != "success" && $E2E_STATUS != "not_run" && $E2E_STATUS != "error" && $E2E_STATUS != "unauthorized" ]]
          do
            sleep 30s
            echo "Polling test job ${TEST_JOB_WORKFLOW_ID}..."
            E2E_RESULT=$(curl --location -sS --request GET "https://circleci.com/api/v2/pipeline/${TEST_JOB_WORKFLOW_ID}/workflow" --header "Circle-Token: ${CCI_PAT}")
            echo $E2E_RESULT;
            E2E_STATUS=$(echo $E2E_RESULT | jq --raw-output '.items[0].status')
            if [ $E2E_STATUS = "null" ]; then
              echo "Problem extracting status from workflow API! Aborting!";
              exit 1
            fi
            echo "Status is now: $E2E_STATUS"
          done
          if [ $E2E_STATUS = "failed" ] || [ $E2E_STATUS = "error" ]; then
            E2E_PIPE_NUM=$(echo $E2E_RESULT | jq '.items[0].pipeline_number')
            E2E_WORKFLOW_ID=$(echo $E2E_RESULT | jq -r '.items[0].id')
            CIRCLE_FAIL_LINK="https://app.circleci.com/pipelines/github/slackapi/platform-devxp-test/${E2E_PIPE_NUM}/workflows/${E2E_WORKFLOW_ID}"
            echo "Tests failed! Visit $CIRCLE_FAIL_LINK for more info."
            exit 1
          elif [ "$E2E_STATUS" = "canceled" ] || [ "$E2E_STATUS" = "unauthorized" ] || [ $E2E_STATUS = "not_run" ]; then
            echo "Tests have been ${E2E_STATUS} and did not finish!"
            exit 1
          else
            echo "Tests passed woot 🎉"
          fi


================================================
FILE: .github/workflows/npm-publish.yml
================================================
# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created
# For more information see: https://help.github.com/actions/language-and-framework-guides/publishing-nodejs-packages

name: Build & Deploy to NPM

on:
  push:
    tags:
      - "*.*.*"

jobs:
  build:
    runs-on: macos-latest
    permissions:
      contents: read
    steps:
      - name: Actions checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup node
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: latest
          registry-url: https://registry.npmjs.org/

      - name: Setup Deno
        uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
        with:
          deno-version: v2.x

      - name: Get the tag name
        id: get_tag_name
        run: echo "TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT

      - name: Run build_npm.ts
        run: deno run -A scripts/build_npm.ts "$TAG"
        env:
          TAG: ${{steps.get_tag_name.outputs.TAG}}

      - name: Publish to NPM
        run: cd npm && npm publish --access=public
        env:
          NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}


================================================
FILE: .github/workflows/npm.yml
================================================
# This workflow runs a test build for npm against changes on main or PRs

name: Npm Build

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  build:
    runs-on: macos-latest
    strategy:
      fail-fast: false
      matrix:
        deno-version:
          - v1.x
          - v2.x
    permissions:
      contents: read
    steps:
      - name: Actions checkout
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false

      - name: Setup node
        uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: latest
          registry-url: https://registry.npmjs.org/

      - name: Setup Deno ${{ matrix.deno-version }}
        uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
        with:
          deno-version: ${{ matrix.deno-version }}

      - name: Run build_npm.ts
        run: deno run -A scripts/build_npm.ts


================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish

on:
  push:
    tags:
      - '[0-9]+.[0-9]+.[0-9]+'

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write # The OIDC ID token is used for authentication with JSR.
    steps:
      - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          persist-credentials: false
      - run: npx jsr publish --allow-slow-types


================================================
FILE: .github/workflows/samples.yml
================================================
# This workflow runs a `deno check` against slack sample apps
name: Samples Integration Type-checking

on:
  push:
    branches:
      - main
  pull_request:
    branches:
      - main

jobs:
  samples:
    runs-on: ubuntu-latest
    strategy:
      fail-fast: false
      matrix:
        sample:
          - slack-samples/deno-issue-submission
          - slack-samples/deno-starter-template
          - slack-samples/deno-blank-template
          - slack-samples/deno-message-translator
          - slack-samples/deno-request-time-off
          - slack-samples/deno-simple-survey
        deno-version:
          - v1.x
          - v2.x
    permissions:
      contents: read
    steps:
      - name: Setup Deno ${{ matrix.deno-version }}
        uses: denoland/setup-deno@667a34cdef165d8d2b2e98dde39547c9daac7282 # v2.0.4
        with:
          deno-version: ${{ matrix.deno-version }}

      - name: Checkout the sdk
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          path: ./deno-slack-sdk
          persist-credentials: false

      - name: Checkout the ${{ matrix.sample }} sample
        uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
        with:
          repository: ${{ matrix.sample }}
          path: ./sample
          persist-credentials: false

      - name: Set imports.deno-slack-sdk/ to ../deno-slack-sdk/src/ in imports
        run: >
          deno run
          --allow-read --allow-write 
          deno-slack-sdk/scripts/imports/update.ts 
          --import-file "./sample/deno.jsonc"
          --sdk "./deno-slack-sdk/"

      - name: Deno check **/*.ts
        working-directory: ./sample
        run: find . -type f -regex ".*\.ts" | xargs deno check -r


================================================
FILE: .gitignore
================================================
.DS_Store
.coverage
.vim
lcov.info
npm/


================================================
FILE: .vscode/settings.json
================================================
{
  "deno.enable": true,
  "deno.lint": true,
  "deno.config": "./deno.jsonc",
  "[typescript]": {
    "editor.formatOnSave": true,
    "editor.defaultFormatter": "denoland.vscode-deno"
  }
}


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) Slack Technologies, Inc.

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.


================================================
FILE: README.md
================================================
<h1 align="center">
  Deno Slack SDK
  <br>
</h1>

<p align="center">
    <img alt="deno.land version" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Flatest-version%2Fx%2Fdeno_slack_sdk%2Fmod.ts">
    <img alt="deno dependencies" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Fupdates%2Fx%2Fdeno_slack_sdk%2Fmod.ts">
    <img alt="Samples Integration Type-checking" src="https://github.com/slackapi/deno-slack-sdk/workflows/Samples%20Integration%20Type-checking/badge.svg">
  </a>
</p>

A Deno SDK to build Slack apps with the latest platform features. Read the
[quickstart guide](https://api.slack.com/automation/quickstart) and look at our
[code samples](https://api.slack.com/automation/samples) to learn how to build
apps.

## Versioning

Releases for this repository follow the [SemVer](https://semver.org/) versioning
scheme. The SDK's contract is determined by the top-level exports from
`src/mod.ts` and `src/types.ts`. Exports not included in these files are deemed
internal and any modifications will not be treated as breaking changes. As such,
internal exports should be treated as unstable and used at your own risk.

## Setup

Make sure you have a development workspace where you have permission to install
apps. **Please note that the features in this project require that the workspace
be part of [a Slack paid plan](https://slack.com/pricing).**

### Install the Slack CLI

You need to install and configure the Slack CLI. Step-by-step instructions can
be found on our
[install & authorize page](https://api.slack.com/automation/cli/install).

## Creating an app

Create a blank project by executing the following command:

```zsh
slack create my-app --template slack-samples/deno-blank-template

cd my-app/
```

The `manifest.ts` file contains the app's configuration. This file defines
attributes like app name, description and functions.

### Create a [function](https://api.slack.com/automation/functions/custom)

```zsh
mkdir ./functions && touch ./functions/hello_world.ts
```

```ts
// Contents of ./functions/hello_world.ts
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";

export const HelloWorldFunctionDef = DefineFunction({
  callback_id: "hello_world_function",
  title: "Hello World",
  source_file: "functions/hello_world.ts",
  input_parameters: {
    properties: {},
    required: [],
  },
  output_parameters: {
    properties: {
      message: {
        type: Schema.types.string,
        description: "Hello world message",
      },
    },
    required: ["message"],
  },
});

export default SlackFunction(
  HelloWorldFunctionDef,
  () => {
    return {
      outputs: { message: "Hello World!" },
    };
  },
);
```

`DefineFunction` is used to define a custom function and provide Slack with the
information required to use it.

`SlackFunction` uses the definition returned by `DefineFunction` and your custom
executable code to export a Slack-usable custom function.

### Create a [workflow](https://api.slack.com/automation/workflows)

```zsh
mkdir ./workflows && touch ./workflows/hello_world.ts
```

```ts
// Contents of ./workflows/hello_world.ts
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { HelloWorldFunctionDef } from "../functions/hello_world.ts";

const HelloWorldWorkflowDef = DefineWorkflow({
  callback_id: "hello_world_workflow",
  title: "Hello World Workflow",
  input_parameters: {
    properties: {
      channel: {
        type: Schema.slack.types.channel_id,
      },
    },
    required: ["channel"],
  },
});

const helloWorldStep = HelloWorldWorkflowDef.addStep(HelloWorldFunctionDef, {});

HelloWorldWorkflowDef.addStep(Schema.slack.functions.SendMessage, {
  channel_id: HelloWorldWorkflowDef.inputs.channel,
  message: helloWorldStep.outputs.message,
});

export default HelloWorldWorkflowDef;
```

`DefineWorkflow` is used to define a workflow and provide Slack with the
information required to use it.

`HelloWorldWorkflow.addStep` is used to add a step to the workflow; here we add
the `HelloWorldFunction` and then the `SendMessage` Slack Function that will
post the `message` to a Slack channel.

### Update the [manifest](https://api.slack.com/automation/manifest)

```ts
// Contents of manifest.ts
import { Manifest } from "deno-slack-sdk/mod.ts";
import HelloWorldWorkflow from "./workflows/hello_world.ts";

export default Manifest({
  name: "my-app",
  description: "A Hello World app",
  icon: "assets/default_new_app_icon.png",
  workflows: [HelloWorldWorkflow],
  outgoingDomains: [],
  botScopes: ["chat:write", "chat:write.public"],
});
```

`Manifest` is used to define your apps
[manifest](https://api.slack.com/automation/manifest) and provides Slack with
the information required to manage it.

### Create a [trigger](https://api.slack.com/automation/triggers)

```zsh
mkdir ./triggers && touch ./triggers/hello_world.ts
```

```ts
// Contents of ./triggers/hello_world.ts
import { Trigger } from "deno-slack-sdk/types.ts";
import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts";
import HelloWorldWorkflow from "../workflows/hello_world.ts";

const trigger: Trigger<typeof HelloWorldWorkflow.definition> = {
  type: TriggerTypes.Shortcut,
  name: "Reverse a string",
  description: "Starts the workflow to reverse a string",
  workflow: `#/workflows/${HelloWorldWorkflow.definition.callback_id}`,
  inputs: {
    channel: {
      value: TriggerContextData.Shortcut.channel_id,
    },
  },
};

export default trigger;
```

The `Trigger` object is used to define a trigger that will invoke the
`HelloWorldWorkflow`. The Slack CLI will detect this file and prompt you for its
creation.

## Running an app

```zsh
slack run
```

When prompted, create the `triggers/hello_world.ts` trigger. This will send your
trigger configuration to Slack.

Post the `Hello world shortcut trigger` in a slack message and **use it**

## Getting Help

[This documentation](https://api.slack.com/automation) has more information on
basic and advanced concepts of the SDK.

Information on how to get started developing with Deno can be found in
[this documentation](https://api.slack.com/automation/deno/develop).

If you get stuck, we're here to help. The following are the best ways to get
assistance working through your issue:

- [Issue Tracker](https://github.com/slackapi/deno-slack-sdk/issues?q=is%3Aissue)
  for questions, bug reports, feature requests, and general discussion. **Try
  searching for an existing issue before creating a new one.**
- Email our developer support team: `support@slack.com`

## Contributing

Contributions are more than welcome. Please look at the
[contributing guidelines](https://github.com/slackapi/deno-slack-sdk/blob/main/.github/CONTRIBUTING.md)
for more info!


================================================
FILE: deno.jsonc
================================================
{
  "$schema": "https://deno.land/x/deno/cli/schemas/config-file.v1.json",
  "name": "@slack/sdk",
  "version": "2.15.2",
  "exports": {
    ".": "./src/mod.ts",
    "./mod.ts": "./src/mod.ts",
    "./types.ts": "./src/types.ts"
  },
  "publish": {
    "exclude": ["**/*_test.ts", "**/fixtures"],
    "include": [
      "README.md",
      "LICENSE",
      "deno.jsonc",
      "src/**"
    ]
  },
  "fmt": {
    "include": ["src", "tests", "docs", "README.md", "scripts", ".github/maintainers_guide.md", ".github/CONTRIBUTING.md"],
    "exclude": ["src/schema/slack/functions/_scripts/functions.json"],
    "semiColons": true,
    "indentWidth": 2,
    "lineWidth": 80,
    "proseWrap": "always",
    "singleQuote": false,
    "useTabs": false
  },
  "lint": {
    "include": ["src", "tests", "scripts"],
    "exclude": ["src/schema/slack/functions/_scripts/functions.json", "**/*.md"],
    "rules": { "exclude": ["no-import-prefix", "no-slow-types"] }
  },
  "tasks": {
    "test": "deno fmt --check && deno lint && deno run --allow-run --allow-env --allow-read scripts/bundle.ts && deno test --allow-read --allow-run --parallel src/ tests/",
    "coverage": "rm -rf .coverage && deno test --reporter=dot --parallel --allow-read --coverage=.coverage src/ && deno coverage --exclude=fixtures --exclude=_test --lcov --output=lcov.info .coverage",
    "publish:dry-run": "deno publish --allow-slow-types --dry-run"
  },
  "lock": false
}


================================================
FILE: docs/datastores.md
================================================
## Datastores

### Defining a Datastore

Datastores can be defined with the top level `DefineDatastore` export. Below is
an example of setting up a Datastore:

```ts
import { DefineDatastore, Schema } from "deno-slack-sdk/mod.ts";

export const ReversalsDatastore = DefineDatastore({
  name: "reversals",
  attributes: {
    id: {
      type: Schema.types.string,
    },
    original: {
      type: Schema.types.string,
    },
    reversed: {
      type: Schema.types.string,
    },
  },
  primary_key: "id",
});
```

### Registering a Datastore to the App

To register the newly defined Datastore, add it to the array assigned to the
`datastores` parameter while defining the [`Manifest`][manifest].

```ts
export default Manifest({
  name: "admiring-ox-50",
  description: "Reverse a string",
  icon: "assets/icon.png",
  functions: [ReverseFunction],
  outgoingDomains: [],
  datastores: [ReversalsDatastore],
  botScopes: [
    "commands",
    "chat:write",
    "chat:write.public",
    "datastore:read",
    "datastore:write",
  ],
});
```

Note: Registering a Datastore will automatically add `datastore:read` and
`datastore:write` to the App's defined `botScopes`.

### Using a Datastore in your custom function code

Now that you have a Datastore all set up, you can use it in your
[`functions`][functions]! Import the
[deno-slack-api](https://github.com/slackapi/deno-slack-api) library,
instantiate your client, and make an API call to one of the Datastore endpoints!

```ts
import { SlackFunction } from "deno_slack_api/mod.ts";

export default SlackFunction(ReverseFunction, async ({ client, inputs }) => {
  const original = inputs.stringToReverse;
  const recordId = crypto.randomUUID();
  const reversed = inputs.stringToReverse.split("").reverse().join("");

  const putResp = await client.apps.datastore.put({
    datastore: "reversals",
    item: {
      id: recordId,
      original,
      reversed,
    },
  });
  if (!putResp.ok) {
    return {
      error: putResp.error,
    };
  }
  // ...
});
```

[functions]: ./functions.md
[manifest]: ./manifest.md


================================================
FILE: docs/events.md
================================================
## Events

Custom events provide a way for Apps to validate
[message metadata](https://api.slack.com/metadata) against a pre-defined schema.

### Defining an event

Events can be defined with the top level `DefineEvent` export. Events must be
set up as an `object` type or a [`custom Type`][types] of an `object` type.
Below is an example of setting up a custom Event that can be used during an
incident.

```ts
const IncidentEvent = DefineEvent({
  name: "incident",
  title: "Incident",
  type: Schema.types.object,
  properties: {
    id: { type: Schema.types.string },
    title: { type: Schema.types.string },
    summary: { type: Schema.types.string },
    severity: { type: Schema.types.string },
    date_created: { type: Schema.types.number },
  },
  required: ["id", "title", "summary", "severity"],
  additionalProperties: false, // Setting this to false forces the validation to catch any additional properties
});
```

### Registering an event with the app

To register the newly defined event, add it to the array assigned to the
`events` parameter while defining the [`Manifest`][manifest].

Note: All custom events **must** be registered to the [Manifest][manifest] in
order for them to be used. There is no automated registration for events.

```ts
Manifest({
  ...
  events: [IncidentEvent],
});
```

### Referencing events

There are two places where you can reference your events:

1. Posting a message to Slack
2. Creating a message metadata trigger

#### Posting a message to Slack

Event validation happens against the App's manifest when an App posts a message
to Slack using the
[`metadata` parameter](https://api.slack.com/methods/chat.postMessage#arg_metadata).
If the `event_type` matches the `name` of a custom Event specified in the App's
manifest, it will validate that all required parameters are provided. If it
doesn't meet the validation standards, a warning will be returned in the
response and the message will still be posted, but the metadata will be dropped
from the message.

```ts
// At workflow authoring time
// This example assumes all required values are passed to the workflow's inputs
MyWorkflow.addStep(Schema.slack.functions.SendMessage, {
  channel_id: MyWorkflow.inputs.channel_id,
  message: "We have an incident!",
  metadata: {
    event_type: IncidentEvent,
    event_payload: {
      id: MyWorkflow.inputs.incident_id,
      title: MyWorkflow.inputs.incident_title,
      summary: MyWorkflow.inputs.incident_summary,
      severity: MyWorkflow.inputs.incident_severity,
      date_created: MyWorkflow.inputs.incident_date, // Since this isn't required, it doesn't need to exist to pass validation
    },
  },
});
```

```ts
// At function runtime
// This example assumes all required values are passed to the function's inputs
await client.chat.postMessage({
  channel_id: inputs.channel_id,
  message: "We have an incident!",
  metadata: {
    event_type: IncidentEvent,
    event_payload: {
      id: inputs.incident_id,
      title: inputs.incident_title,
      summary: inputs.incident_summary,
      severity: inputs.incident_severity,
      date_created: inputs.incident_date, // Since this isn't required, it doesn't need to exist to pass validation
    },
  },
});
```

#### Creating a message metadata trigger

Now that the app has a defined schema for the event, a trigger can be created to
watch for any message posted with the expected metadata. When the schema is met,
the trigger will execute a workflow

```ts
// A trigger Definition file for the CLI
import { IncidentEvent } from "./manifest.ts";

const trigger: Trigger = {
  type: "event",
  name: "Incident Metadata Posted",
  inputs: {
    id: "{{data.metadata.event_payload.incident_id}}",
    title: "{{data.metadata.event_payload.incident_title}}",
    summary: "{{data.metadata.event_payload.incident_summary}}",
    severity: "{{data.metadata.event_payload.incident_severity}}",
    date_created: "{{data.metadata.event_payload.incident_date}}",
  },
  workflow: "#/workflows/start_incident",
  event: {
    event_type: "slack#/events/message_metadata_posted",
    metadata_event_type: IncidentEvent,
    channel_ids: ["C012354"], // The channel that needs to be watched for message metadata being posted
  },
};

export default trigger;
```

[manifest]: ./manifest.md
[types]: ./types.md


================================================
FILE: docs/functions-action-handlers.md
================================================
## Block kit action handlers

Your application's [functions][functions] can do a wide variety of interesting
things: post messages, create channels, or anything available to developers via
the [Slack API][api]. One of the more compelling features available to app
developers is the ability to use [Block Kit][block-kit] to add richness and
depth to messages in Slack. Even better, [Block Kit][block-kit] supports a
variety of [interactive components][interactivity]! This document explores the
APIs available to app developers building Run-On-Slack applications to leverage
these [interactive components][interactivity] and how applications can respond
to user interactions with these [interactive components][interactivity].

If you're already familiar with the main concepts underpinning Block Kit Action
Handlers, then you may want to skip ahead to the
[`addBlockActionsHandler()` method API Reference](#api-reference).

- [Block kit action handlers](#block-kit-action-handlers)
  - [Requirements](#requirements)
  - [Posting a message with block kit elements](#posting-a-message-with-block-kit-elements)
  - [Adding block action handlers](#adding-block-action-handlers)
  - [API reference](#api-reference)
    - [`addBlockActionsHandler(constraint, handler)`](#addblockactionshandlerconstraint-handler)
      - [`BlockActionConstraintField`](#blockactionconstraintfield)
        - [`BlockActionConstraintObject`](#blockactionconstraintobject)

### Requirements

Your app needs to have an existing [function][functions] defined, implemented
and working before you can add interactivity handlers like Block Kit Action
Handlers to them. Make sure you have followed our
[functions documentation][functions] and have a function in your app ready that
we can expand with a Block Kit Action Handler.

As part of exploring how Block Kit Action Handlers work, we'll walk through an
approval flow example. A user would trigger our app's function, which would post
a message with two buttons: Approve and Deny. Once someone clicks either button,
our app will handle these button interactions - these Block Kit Actions - and
update the original message with either an "Approved!" or "Denied!" text.

For the purposes of walking through this approval flow example, let us assume
the following [function][functions] definition (that we will store in a file
called `definition.ts` under the `functions/approval/` subdirectory inside your
app):

```typescript
import { DefineFunction, Schema } from "deno-slack-sdk/mod.ts";

export const ApprovalFunction = DefineFunction({
  callback_id: "review_approval",
  title: "Approval",
  description: "Get approval for a request",
  source_file: "functions/approval/mod.ts", // <-- important! Make sure this is where the logic for your function - which we will write in the next section - exists.
  input_parameters: {
    properties: {
      requester_id: {
        type: Schema.slack.types.user_id,
        description: "Requester",
      },
      approval_channel_id: {
        type: Schema.slack.types.channel_id,
        description: "Approval channel",
      },
    },
    required: [
      "requester_id",
      "approval_channel_id",
    ],
  },
  output_parameters: {
    properties: {
      approved: {
        type: Schema.types.boolean,
        description: "Approved",
      },
      reviewer: {
        type: Schema.slack.types.user_id,
        description: "Reviewer",
      },
      message_ts: {
        type: Schema.types.string,
        description: "Request Message TS",
      },
    },
    required: ["approved", "reviewer", "message_ts"],
  },
});
```

### Posting a message with block kit elements

First, we need a message that has some [interactive components][interactivity]
from [Block Kit][block-kit] included! We can modify one of our app's
[functions][functions] to post a message that includes some interactive
components. Here's an example function (which we will assume exists in a
`mod.ts` file under the `functions/approval/` subdirectory in your app) that
posts a message with two buttons: an approval button, and a deny button:

```typescript
import { SlackFunction } from "deno-slack-sdk/mod.ts";
// ApprovalFunction is the function we defined in the previous section
import { ApprovalFunction } from "./definition.ts";

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",
        },
        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,
  };
});
```

The key bit of information we need to remember before moving on to adding an
action handler are the `action_id` and `block_id` properties defined in the
`blocks` payload. Using these IDs, we will be able to differentiate between the
different button components that users interacted with in this message.

### Adding block action handlers

The [Deno Slack SDK][sdk] - which comes bundled in your generated Run-on-Slack
application - provides a means for defining a handler to execute every time a
user interacts with an interactive Block Kit element created by your function.

Continuing with our above example, we can now define a handler that will listen
for actions on one of the interactive components we attached to the message our
main function posted: either the approve button being clicked or the deny button
being clicked. The code to add a Block Kit action handler is "chained" off of
your top-level function, and would look like this:

```typescript
export default SlackFunction(ApprovalFunction, async ({ inputs, client }) => {
  // ... the rest of your ApprovalFunction logic here ...
}).addBlockActionsHandler(
  ["approve_request", "deny_request"], // The first argument to addBlockActionsHandler 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, client }) => { // The second argument is the handler function itself
    console.log("Incoming action handler invocation", action);

    const outputs = {
      reviewer: body.user.id,
      // Based on which button was pressed - determined via action_id - we can
      // determine whether the request was approved or not.
      approved: action.action_id === "approve_request",
      message_ts: body.message.ts,
    };

    // Remove the button from the original message using the chat.update API
    // and replace its contents with the result of the approval.
    await client.chat.update({
      channel: body.function_data.inputs.approval_channel_id,
      ts: outputs.message_ts,
      blocks: [{
        type: "context",
        elements: [
          {
            type: "mrkdwn",
            text: `${
              outputs.approved ? " :white_check_mark: Approved" : ":x: Denied"
            } by <@${outputs.reviewer}>`,
          },
        ],
      }],
    });

    // And now we can mark the function as 'completed' - which is required as
    // we explicitly marked it as incomplete in the main function handler.
    await client.functions.completeSuccess({
      function_execution_id: body.function_data.execution_id,
      outputs,
    });
  },
);
```

Now when you run your app and trigger your function, you have the basics in
place to provide interactivity between your application and users in Slack!

### API reference

#### `addBlockActionsHandler(constraint, handler)`

```typescript
SlackFunction({ ... }).addBlockActionsHandler({ block_id: "mah-buttons", action_id: "approve_request"}, async (ctx) => { ... });
```

`addHandler` registers a block action handler based on a `constraint` argument.
If any incoming actions match the `constraint`, then the specified `handler`
will be invoked with the action. This allows for authoring focussed,
single-purpose action handlers and provides a concise but flexible API for
registering handlers to specific actions.

`constraint` is of type [`BlockActionConstraint`][constraint], which itself can
be either a [`BlockActionConstraintField`](#blockactionconstraintfield) or a
[`BlockActionConstraintObject`](#blockactionconstraintobject).

If a [`BlockActionConstraintField`](#blockactionconstraintfield) is used as the
value for `constraint`, then this will be matched against the incoming action's
`action_id` property.

[`BlockActionConstraintObject`](#blockactionconstraintobject) is a more complex
object used to match against actions. It contains nested `block_id` and
`action_id` properties - both optional - that are used to match against the
incoming action.

##### `BlockActionConstraintField`

```typescript
type BlockActionConstraintField = string | string[] | RegExp;
```

- when provided as a `string`, it must match the field exactly.
- when provided as an array of `string`s, it must match one of the array values
  exactly.
- when provided as a `RegExp`, the regular expression must match.

###### `BlockActionConstraintObject`

```typescript
type BlockActionConstraintObject = {
  block_id?: BlockActionConstraintField;
  action_id?: BlockActionConstraintField;
};
```

This object can contain two properties, both optional: `action_id` and/or
`block_id`. The type of each property is
[`BlockActionConstraintField`](#blockactionconstraintfield).

If both `action_id` and `block_id` properties exist on the `constraint`, then
both `action_id` and `block_id` properties _must match_ any incoming action. If
only one of these properties is provided, then only the provided property must
match.

[functions]: ./functions.md
[api]: https://api.slack.com/methods
[block-kit]: https://api.slack.com/block-kit
[interactivity]: https://api.slack.com/block-kit/interactivity
[sdk]: https://github.com/slackapi/deno-slack-sdk
[constraint]: ../src/functions/routers/types.ts#L53-L62


================================================
FILE: docs/functions-suggestion-handlers.md
================================================
## Block Kit suggestion handlers

Your application's [functions][functions] can do a wide variety of interesting
things: post messages, create channels, or anything available to developers via
the [Slack API][api]. One of the more compelling features available to app
developers is the ability to use [Block Kit][block-kit] to add richness and
depth to messages in Slack. Even better, [Block Kit][block-kit] supports a
variety of [interactive components][interactivity]! This document explores how
to provide dynamic menu options for
[external-data-sourced Block Kit drop-down menus](https://api.slack.com/reference/block-kit/block-elements#external_select).

If you're already familiar with the main concepts underpinning Block Kit
Suggestion Handlers, then you may want to skip ahead to the
[`addBlockSuggestionHandler()` method API Reference](#api-reference).

Worthwhile noting that while this document covers how to render custom menu
options for external-data-sourced Block Kit drop-down menus, it does _not_ cover
how to respond to a user selecting one of the custom menu options. Do not fear,
though! The same approach discussed in our
[Block Kit Action Handlers][action-handlers] document can be used to register an
action handler to respond to drop-down menu selections.

- [Block Kit suggestion handlers](#block-kit-suggestion-handlers)
  - [Requirements](#requirements)
  - [Posting a message with block kit elements](#posting-a-message-with-block-kit-elements)
  - [Adding Block Suggestion Handlers](#adding-block-suggestion-handlers)
  - [API Reference](#api-reference)
    - [`addBlockSuggestionHandler(constraint, handler)`](#addblocksuggestionhandlerconstraint-handler)
      - [`BlockActionConstraintField`](#blockactionconstraintfield)
        - [`BlockActionConstraintObject`](#blockactionconstraintobject)

### Requirements

Your app needs to have an existing [function][functions] defined, implemented
and working before you can add interactivity handlers like Block Kit Suggestion
Handlers to them. Make sure you have followed our
[functions documentation][functions] and have a function in your app ready that
we can expand with interactivity. Familiarity with the
[Block Kit Actions Handlers][action-handlers] would be a huge plus as the
handling Block Kit Actions and handling Block Kit Suggestions is practically
identical.

As part of exploring how Block Kit Suggestion Handlers work, we'll walk through
an example that posts an inspirational quote. A user would trigger our app's
function, which would post a message with a drop down select menu and a button.
The options rendered in the select menu will be dynamically loaded from an
external API. Finally, when someone has selected a drop-down menu option and
clicked the button, our app can post the selection to the channel (note: for the
purposes of describing how to respond to the select menu interactions, we won't
cover handling the button click or posting the selection in this document).

For the purposes of walking through this example, let us assume the following
[function][functions] definition (that we will store in a file called
`definition.ts` under the `functions/quote/` subdirectory inside your app):

```typescript
import { DefineFunction, Schema } from "deno-slack-sdk/mod.ts";

export const QuoteFunction = DefineFunction({
  callback_id: "quote",
  title: "Inspire Me",
  description: "Get an inspirational quote",
  source_file: "functions/quote/mod.ts", // <-- important! Make sure this is where the logic for your function - which we will write in the next section - exists.
  input_parameters: {
    properties: {
      requester_id: {
        type: Schema.slack.types.user_id,
        description: "Requester",
      },
      channel_id: {
        type: Schema.slack.types.channel_id,
        description: "Channel",
      },
    },
    required: [
      "requester_id",
      "channel_id",
    ],
  },
  output_parameters: {
    properties: {
      quote: {
        type: Schema.types.string,
        description: "Quote",
      },
    },
    required: ["quote"],
  },
});
```

### Posting a message with block kit elements

First, we need a message that has some [interactive components][interactivity]
from [Block Kit][block-kit] included! We can modify one of our app's
[functions][functions] to post a message that includes some interactive
components - including our external select drop down menu. Here's an example
function (which we will assume exists in a `mod.ts` file under the
`functions/quote/` subdirectory in your app) that posts a message with a
external data select drop down menu:

```typescript
import { SlackFunction } from "deno-slack-sdk/mod.ts";
// QuoteFunction is the function we defined in the previous section
import { QuoteFunction } from "./definition.ts";

export default SlackFunction(QuoteFunction, async ({ inputs, client }) => {
  console.log("Incoming quote request!");

  await client.chat.postMessage({
    channel: inputs.channel_id,
    blocks: [{
      "type": "actions",
      "block_id": "so-inspired",
      "elements": [{
        type: "external_select",
        placeholder: {
          type: "plain_text",
          text: "Inspire",
        },
        action_id: "ext_select_input",
      }, {
        type: "button",
        text: {
          type: "plain_text",
          text: "Post",
        },
        action_id: "post_quote",
      }],
    }],
  });
  // Important to set completed: false! We should set the function's complete
  // status later - in the action handler responding to the button click
  return {
    completed: false,
  };
});
```

The key bit of information we need to remember before moving on to adding a
suggestion handler are the `action_id` and `block_id` properties defined in the
`blocks` payload. Using these IDs, we will be able to differentiate between the
different Block Kit components that users interacted with in this message.

### Adding Block Suggestion Handlers

The [Deno Slack SDK][sdk] - which comes bundled in your generated Run-on-Slack
application - provides a means for defining a handler to execute every time a
user interacts with an interactive Block Kit element created by your function.

Continuing with our above example, we can now define a handler that will listen
for interactions with the external data drop down menu. The code to add a Block
Kit suggestion handler is "chained" off of your top-level function, and would
look like this:

```typescript
export default SlackFunction(QuoteFunction, async ({ inputs, client }) => {
  // ... the rest of your QuoteFunction logic here ...
}).addBlockSuggestionHandler(
  "ext_select_input", // The first argument to addBlockActionsHandler can accept an action_id string, among many other formats!
  // Check the API reference at the end of this document for the full list of supported options
  async ({ body, client }) => { // The second argument is the handler function itself
    console.log("Incoming suggestion handler invocation", body);
    // Fetch some inspirational quotes
    const apiResp = await fetch(
      "https://motivational-quote-api.herokuapp.com/quotes",
    );
    const quotes = await apiResp.json();
    console.log("Returning", quotes.length, "quotes");
    const opts = {
      "options": quotes.map((q) => ({
        value: `${q.id}`,
        text: { type: "plain_text", text: q.quote.slice(0, 70) },
      })),
    };
    return opts;
  },
);
```

### API Reference

#### `addBlockSuggestionHandler(constraint, handler)`

```typescript
SlackFunction({ ... }).addBlockSuggestionHandler({ block_id: "mah-buttons", action_id: "approve_request"}, async (ctx) => { ... });
```

`addBlockSuggestionHandler` registers a block suggestion handler based on a
`constraint` argument. If any incoming suggestion events match the `constraint`,
then the specified `handler` will be invoked with the suggestion payload. This
allows for authoring focussed, single-purpose suggestion handlers and provides a
concise but flexible API for registering handlers to specific
external-data-sourced drop down menu.

`constraint` is of type [`BlockActionConstraint`][constraint], which itself can
be either a [`BlockActionConstraintField`](#blockactionconstraintfield) or a
[`BlockActionConstraintObject`](#blockactionconstraintobject).

If a [`BlockActionConstraintField`](#blockactionconstraintfield) is used as the
value for `constraint`, then this will be matched against the incoming action's
`action_id` property.

[`BlockActionConstraintObject`](#blockactionconstraintobject) is a more complex
object used to match against actions. It contains nested `block_id` and
`action_id` properties - both optional - that are used to match against the
incoming suggestion.

##### `BlockActionConstraintField`

```typescript
type BlockActionConstraintField = string | string[] | RegExp;
```

- when provided as a `string`, it must match the field exactly.
- when provided as an array of `string`s, it must match one of the array values
  exactly.
- when provided as a `RegExp`, the regular expression must match.

###### `BlockActionConstraintObject`

```typescript
type BlockActionConstraintObject = {
  block_id?: BlockActionConstraintField;
  action_id?: BlockActionConstraintField;
};
```

This object can contain two properties, both optional: `action_id` and/or
`block_id`. The type of each property is
[`BlockActionConstraintField`](#blockactionconstraintfield).

If both `action_id` and `block_id` properties exist on the `constraint`, then
both `action_id` and `block_id` properties _must match_ any incoming suggestion.
If only one of these properties is provided, then only the provided property
must match.

[functions]: ./functions.md
[action-handlers]: ./functions-action-handlers.md
[api]: https://api.slack.com/methods
[block-kit]: https://api.slack.com/block-kit
[interactivity]: https://api.slack.com/block-kit/interactivity
[sdk]: https://github.com/slackapi/deno-slack-sdk
[constraint]: ../src/functions/routers/types.ts#L53-L62


================================================
FILE: docs/functions-view-handlers.md
================================================
## View Handlers

Your application's [functions][functions] can do a wide variety of interesting
things: post messages, create channels, or anything available to developers via
the [Slack API][api]. They can even include
[interactive components][interactivity] or pop up a [Modal][modals].
[Modals][modals] are composed of up to three [Views][views]. These
[Views][views] can contain form inputs or
[interactive components][interactivity]. [Views][views] themselves may also
[trigger events][view-events]. This document explores the APIs available to app
developers building Run-On-Slack applications to create [modals][modals]
composed of [views][views] and how applications can respond to the
[view submission and closed events][view-events] they can trigger.

If you're already familiar with the main concepts underpinning View Handlers,
then you may want to skip ahead to the [API Reference](#api-reference).

- [View Handlers](#view-handlers)
  - [Requirements](#requirements)
  - [Opening a view](#opening-a-view)
    - [Opening a view from a custom function](#opening-a-view-from-a-custom-function)
    - [Opening a view from a block action handler](#opening-a-view-from-a-block-action-handler)
  - [Adding view handlers](#adding-view-handlers)
  - [API reference](#api-reference)
    - [`addViewSubmissionHandler(constraint, handler)`](#addviewsubmissionhandlerconstraint-handler)
      - [`addViewClosedHandler(constraint, handler)`](#addviewclosedhandlerconstraint-handler)

### Requirements

This functionality requires at least version 0.2.0 of the
[`deno-slack-sdk`][sdk].

Your app needs to have an existing [function][functions] defined, implemented
and working before you can add interactivity handlers like View Handlers or
[Block Kit Action Handlers][action-handlers] to them. Make sure you have
followed our [functions documentation][functions] and have a function in your
app ready that we can expand with a View Handler.

As part of exploring how View Handlers work, we'll walk through a simple diary
flow example. It is nothing more than a contrived example aimed at showing off
the APIs. A user would trigger our app's function, which would open a view with
a single text input. If the view is submitted with content, the application will
send the user a DM with their inputted content. If the view is closed, the
application will send the user a DM encouraging them not to give up on their
diarying habit.

For the purposes of walking through this approval flow example, let us assume
the following [function][functions] definition (that we will store in a file
called `definition.ts` under the `functions/diary/` subdirectory inside your
app):

```typescript
import { DefineFunction, Schema } from "deno-slack-sdk/mod.ts";

export const DiaryFunction = DefineFunction({
  callback_id: "diary",
  title: "Diary",
  description: "Write a diary entry",
  source_file: "functions/diary/mod.ts", // <-- important! Make sure this is where the logic for your function - which we will write in the next section - exists.
  input_parameters: {
    properties: {
      interactivity: { // <-- important! This gives Slack a hint that your function will create interactive elements like views
        type: Schema.slack.types.interactivity,
      },
      channel_id: {
        type: Schema.slack.types.channel_id,
      },
    },
    required: ["interactivity"],
  },
  output_parameters: {
    properties: {},
    required: [],
  },
});
```

### Opening a view

[Opening a view via the `views.open` API][views-open] and
[pushing a new view onto the view stack via the `views.push` API][views-push]
both require the use of a [`trigger_id`][trigger-ids]. These are identifiers
representing specific user interactions. Slack uses these to prevent
applications from haphazardly opening modals in users' faces willy-nilly.
Without a `trigger_id`, your application can't create a modal and open a view.
FYI `trigger_id`s are also known as `interactivity_pointer`s.

As such, there are two ways to open a view from inside a Run-On-Slack
application: doing so
[from a function directly](#opening-a-view-from-a-function) vs. doing so
[from a Block Action Handler](#opening-a-view-from-a-block-action-handler). The
sections covering each approach below discuss how to retrieve the `trigger_id`
in each scenario.

We will explore implementing our contrived example above by opening a view from
a function. In a section further below, we will also cover
[opening a view from a Block Action Handler](#opening-a-view-from-a-block-action-handler).

#### Opening a view from a custom function

As mentioned in the previous section, we need to have a `trigger_id` handy in
order to open a view. This is why we defined an `interactivity` input in our
function definition earlier: this input will magically provide us with a
`trigger_id`. The property to use as a `trigger_id` exists on inputs with the
type `Schema.slack.types.interactivity` under the `interactivity_pointer`
property. Check out the code below for an example:

```typescript
import { SlackFunction } from "deno-slack-sdk/mod.ts";
// DiaryFunction is the function we defined in the previous section
import { DiaryFunction } from "./definition.ts";

export default SlackFunction(DiaryFunction, async ({ inputs, client }) => {
  console.log('Someone might want to write a diary entry...');

  await client.views.open({
    trigger_id: inputs.interactivity.interactivity_pointer,
    view: {
      "type": "modal",
      "title": {
        "type": "plain_text",
        "text": "Modal title",
      },
      "blocks": [
        {
          "type": "input",
          "block_id": "section1",
          "element": {
            "type": "plain_text_input",
            "action_id": "diary_input",
            "multiline": true,
            "placeholder": {
              "type": "plain_text",
              "text": "What is on your mind today?",
            },
          },
          "label": {
            "type": "plain_text",
            "text": "Diary Entry",
          },
          "hint": {
            "type": "plain_text",
            "text": "Don't worry, no one but you will see this.",
          },
        },
      ],
      "close": {
        "type": "plain_text",
        "text": "Cancel",
      },
      "submit": {
        "type": "plain_text",
        "text": "Save",
      },
      "callback_id": "view_identifier_12", // <-- remember this ID, we will use it to route events to handlers!
      "notify_on_close": true, // <-- this must be defined in order to trigger `view_closed` events!
    },
  });
  // Important to set completed: false! We will set the function's complete
  // status later - in our view submission handler
  return {
    completed: false,
  };
};
```

#### Opening a view from a block action handler

If [Block Kit Action Handlers][action-handlers] is a foreign concept to you, we
recommend first checking out [its documentation][action-handlers] before
venturing deeper into this section.

Similarly to opening a view from a function, doing so from a
[Block Action Handler][action-handlers] is straightforward though slightly
different. It is important to remember that `trigger_id`s represent a unique
user interaction with a particular interactive component within Slack's UI. As
such, when responding to a Block Kit Action interactive component, we don't want
to use your function's `inputs` to retrieve the `interactivity_pointer`, as we
did in the previous section, but rather, we want to retrieve a `trigger_id` that
is unique to the Block Kit interactive component.

Luckily for us, this is provided as a parameter to Block Kit Action Handlers!
You can use the value of `body.interactivity.interactivity_pointer` within an
action handler to open a view, like so:

```typescript
export default SlackFunction(DiaryFunction, async ({ inputs, client }) => {
  // ... the rest of your DiaryFunction logic here ...
}).addBlockActionsHandler(
  "deny_request",
  async ({ action, body, client }) => {
    await client.views.open({
      trigger_id: body.interactivity.interactivity_pointer,
      view: {/* your view object goes here */},
    });
  },
);
```

### Adding view handlers

The [Deno Slack SDK][sdk] - which comes bundled in your generated Run-on-Slack
application - provides a means for defining handlers to execute every time a
user interacts with a view. In this way you can route view-related events to
specific handlers inside your application. The key identifier that we'll need to
keep handy is the `callback_id` we assigned to any views we created. This ID
will be the property that determines which view event handler will respond to
incoming view events.

Continuing with our above example, we can now define handlers that will listen
for view submission and closed events and respond accordingly. The code to add
view handlers is "chained" off of your top-level function, and would look like
this:

```typescript
export default SlackFunction(DiaryFunction, async ({ inputs, client }) => {
  // ... the rest of your DiaryFunction logic here ...
}).addViewSubmissionHandler(
  /view/, // The first argument to any of the addView*Handler methods can accept a string, array of strings, or RegExp.
  // This first argument will be used to match the view's `callback_id`
  // Check the API reference at the end of this document for the full list of supported options
  async ({ view, body, token }) => { // The second argument is the handler function itself
    console.log("Incoming view submission handler invocation", body);
  },
)
  .addViewClosedHandler(
    /view/,
    async ({ view, body, token }) => {
      console.log("Incoming view closed handler invocation", body);
    },
  );
```

Importantly, more complex applications will likely be modifying views as users
interact with them: updating the view contents (to e.g. add new form fields),
perhaps pushing a new view onto the view stack to introduce a new UI to the
user, maybe reporting errors to the user for some manner of faulty interaction,
or even clearing the entire view stack altogether. All of these modal
interaction responses are
[covered in depth on our API documentation site][modifying] - make sure to spend
the time to understand the concepts presented there.

In particular, modal interactions can be responded to by using the API, or by
returning particularly-crafted responses directly from inside the view handlers.
On our [API site detailing view modification][modifying], these returned view
handler responses are called `response_action`s.

As an example, consider the following two code snippets. They yield identical
behavior!

```typescript
export default SlackFunction(DiaryFunction, async ({ inputs, client }) => {
  // ... the rest of your DiaryFunction logic here ...
}).addViewSubmissionHandler(/view/, async ({ client, body }) => {
  // A view submission handler that pushes a new view using the API
  await client.views.push({
    trigger_id: body.trigger_id,
    view: {/* your view object goes here */},
  });
}).addSubmissionHandler(/view/, async () => {
  // A view submission handler that pushes a new view using the `response_action`
  return {
    response_action: "push",
    view: {/* your view object goes here */},
  };
});
```

### API reference

#### `addViewSubmissionHandler(constraint, handler)`

```typescript
SlackFunction({ ... }).addViewSubmissionHandler("my_view_callback_id", async (ctx) => { ... });
```

`addViewSubmissionHandler` registers a view handler based on a `constraint`
argument. If any incoming [`view_submission` event][view-events] matches the
`constraint`, then the specified `handler` will be invoked with the event
payload. This allows for authoring focussed, single-purpose view handlers and
provides a concise but flexible API for registering handlers to specific view
interactions.

`constraint` can be either a string, an array of strings, or a regular
expression.

- A simple string `constraint` must match a view's `callback_id` exactly.
- An array of strings `constraint` must match a view's `callback_id` to any of
  the strings in the array.
- A regular expression `constraint` must match a view's `callback_id`.

##### `addViewClosedHandler(constraint, handler)`

```typescript
SlackFunction({ ... }).addViewClosedHandler("my_view_callback_id", async (ctx) => { ... });
```

⚠️ IMPORTANT: you must set a view's `notify_on_close` property to `true` for the
`view_closed` event to trigger; by default this property is `false`. See the
[View reference documentation - in particular the Fields section][view-ref] for
more information.

`addViewClosedHandler` registers a view handler based on a `constraint`
argument. If any incoming [`view_closed` event][view-events] matches the
`constraint`, then the specified `handler` will be invoked with the event
payload. This allows for authoring focussed, single-purpose view handlers and
provides a concise but flexible API for registering handlers to specific view
interactions.

`constraint` can be either a string, an array of strings, or a regular
expression.

- A simple string `constraint` must match a view's `callback_id` exactly.
- An array of strings `constraint` must match a view's `callback_id` to any of
  the strings in the array.
- A regular expression `constraint` must match a view's `callback_id`.

[functions]: ./functions.md
[action-handlers]: ./functions-action-handlers.md
[api]: https://api.slack.com/methods
[block-kit]: https://api.slack.com/block-kit
[interactivity]: https://api.slack.com/block-kit/interactivity
[sdk]: https://github.com/slackapi/deno-slack-sdk
[modals]: https://api.slack.com/surfaces/modals
[views]: https://api.slack.com/surfaces/modals/using
[modifying]: https://api.slack.com/surfaces/modals/using#modifying
[trigger-ids]: https://api.slack.com/interactivity/handling#modal_responses
[view-events]: https://api.slack.com/reference/interaction-payloads/views
[views-methods]: https://api.slack.com/methods?filter=views
[views-open]: https://api.slack.com/methods/views.open
[views-update]: https://api.slack.com/methods/views.update
[views-push]: https://api.slack.com/methods/views.push
[view-ref]: https://api.slack.com/reference/surfaces/views


================================================
FILE: docs/functions.md
================================================
## Custom functions

Functions are the core of your Slack app: they accept one or more input
parameters, execute some logic and return one or more output parameters.

Functions can optionally define different kinds of Interactivity Handlers. If
your function creates messages or opens views, then it may need to define one or
more interactivity handlers to respond to user interactions with these
interactive components. Run-on-Slack applications support the following
interactivity handlers, follow the links to get more information about how each
of them work:

- [Block Kit Action Handlers][action-handlers]: Handle events from interactive
  [Block Kit][block-kit] components that you can use in messages like
  [Buttons, Menus and Date/Time Pickers](https://api.slack.com/block-kit/interactivity)
- [View Handlers][view-handlers]: Handle events triggered from [Modals][modals],
  which are composed of [Views][views].
- [Block Kit Suggestion Handlers][suggest-handlers]: Handle events from
  [external-data-sourced Block Kit select menus](https://api.slack.com/reference/block-kit/block-elements#external_select)

### Defining a custom function

Functions can be defined with the top level `DefineFunction` export. Below is an
example function that turns a `name` input parameter into a dinosaur name:

```ts
import { DefineFunction, Schema } from "slack-cloud-sdk/mod.ts";

export const DinoFunction = DefineFunction({
  callback_id: "dino",
  title: "Dino",
  description: "Turns a name into a dinosaur name",
  source_file: "functions/dino.ts",
  input_parameters: {
    name: {
      type: Schema.types.string,
      description: "The provided name",
    },
  },
  output_parameters: {
    dinoname: {
      type: Schema.types.string,
      description: "The new dinosaur name",
    },
  },
});
```

Let's go over each of the arguments that must be provided to `DefineFunction`.

#### Function definition

The passed argument is the `definition` of the function, an object with a few
properties that help to describe and define the function in more detail. In
particular, the required properties of the object are:

- `callback_id`: A unique string identifier representing the function (`"dino"`
  in the above example). It must be unique in your application; no other
  functions may be named identically. Changing a function's `callback_id` is not
  recommended as it means that the function will be removed from the app and
  created under the new `callback_id`, which will break any workflows
  referencing the old function.
- `title`: A pretty string to nicely identify the function.
- `description`: A short-and-sweet string description of your function
  succinctly summarizing what your function does.
- `source_file`: The relative path from the project root to the function
  `handler` file.
- `input_parameters`: Itself an object which describes one or more input
  parameters that will be available to your function. Each top-level property of
  this object defines the name of one input parameter which will become
  available to your function. The value for this property needs to be an object
  with further sub-properties:
  - `type`: The type of the input parameter. The supported types are `string`,
    `integer`, `boolean`, `number`, `object` and `array`.
  - `description`: A string description of the input parameter.
- `output_parameters`: Itself an object which describes one or more output
  parameters that will be returned by your function. This object follows the
  exact same pattern as `input_parameters`: top-level properties of the object
  define output parameter names, with the property values being an object that
  further describes the `type` and `description` of individual output
  parameters.

### Adding runtime logic to your custom function

Now that you have defined your function's input and output parameters, it's time
to define the body of your function.

First, create a new file at the location set on the `source_file` parameter of
your function definition. Next, let's add code for your function! You will want
to `export default` an instance of `SlackFunction`, like so:

```typescript
import { SlackFunction } from "deno-slack-sdk/mod.ts";

export default SlackFunction(
  // Pass along the function definition you created earlier using `DefineFunction`
  DinoFunction,
  ({ inputs }) => { // Provide any context properties, like `inputs`, `env`, or `token`
    // Implement your function
    const { name } = inputs;
    const dinoname = `${name}asaurus`;

    // Don't forget any required output parameters
    return { outputs: { dinoname } };
  },
);
```

Key points:

- The function takes a single argument, referred to as the
  [function "context"](#function-handler-context).
- The function [returns an object](#function-return-object).

#### Custom function handler context

The single argument to your function is an object composed of several properties
that may be useful to leverage during your function's execution:

- `env`: represents environment variables available to your function's execution
  context.
- `inputs`: an object containing the input parameters you defined as part of
  your function definition. In the example above, the `name` input parameter is
  available on the `inputs` property of our function handler context.
- `client`: An API client ready for use in your function. An instance of the
  `deno-slack-api` library.
- `token`: your application's access token.
- `team_id`: the encoded team (a.k.a. Slack workspace) ID, i.e. T12345.
- `enterprise_id`: the encoded enterprise ID, i.e. E12345. If the Slack
  workspace the function executes in is not a part of an enterprise grid, then
  this value will be the empty string (`""`).
- `event`: an object containing the full incoming event details.

##### Custom function return object

The object returned by your function that supports the following properties:

- `error`: a string indicating the error that was encountered. If present, the
  function will return an error regardless of what is passed to `outputs`.
- `outputs`: an object that exactly matches the structure of your function
  definition's `output_parameters`. This is required unless an `error` is
  returned.
- `completed`: a boolean indicating whether or not the function is completed.
  This defaults to `true`.

### Adding custom functions to the manifest

Once you have defined a function, don't forget to include it in your
[`Manifest`][manifest] definition!

```ts
import { ReverseString } from "./functions/reverse_definition.ts";

Manifest({
  name: "heuristic-tortoise",
  description: "A demo showing how to use custom functions",
  icon: "assets/icon.png",
  botScopes: ["commands", "chat:write", "chat:write.public"],
  functions: [ReverseString], // <-- don't forget this!
});
```

[manifest]: ./manifest.md
[action-handlers]: ./functions-action-handlers.md
[view-handlers]: ./functions-view-handlers.md
[suggest-handlers]: ./functions-suggestion-handlers.md
[block-kit]: https://api.slack.com/block-kit
[modals]: https://api.slack.com/surfaces/modals
[views]: https://api.slack.com/surfaces/modals/using


================================================
FILE: docs/manifest.md
================================================
## Manifest

A Manifest defines your entire Slack application, from its core properties like
its name and description to its behavioural aspects like what
[functions][functions] it contains.

### Defining a manifest

A Manifest can be defined with the top level `Manifest` export. Below is an
example, taken from the template application:

```ts
import { Manifest } from "slack-cloud-sdk/mod.ts";
import { ReverseString } from "./functions/reverse_definition.ts";

export default Manifest({
  name: "heuristic-tortoise-312",
  description: "A demo showing how to use Slack functions",
  icon: "assets/icon.png",
  botScopes: ["commands", "chat:write", "chat:write.public"],
  functions: [ReverseString],
  datastores: [],
  outgoing_domains: [],
});
```

The object passed into the `Manifest` method is the type
[`SlackManifestType`][manifest-type]. Check out [its definition][manifest-type]
for the full list of attributes it supports, but the minimum required properties
are listed in the table below:

| Property      | Type          | Description                                                               |
| ------------- | ------------- | ------------------------------------------------------------------------- |
| `name`        | string        | Your Slack application name.                                              |
| `description` | string        | A short sentence describing your application.                             |
| `icon`        | string        | A relative path to an image asset to use for the application's icon.      |
| `botScopes`   | Array<string> | A list of [scopes][scopes], or permissions, the bot requires to function. |

Furthermore, to set up how your application works, you would create
[functions][functions], and register them in the Manifest using the `functions`
property of [`SlackManifestType`][manifest-type] argument used when creating a
new `Manifest`.

[functions]: ./functions.md
[manifest-type]: ../src/types.ts#L13
[scopes]: https://api.slack.com/scopes


================================================
FILE: docs/types.md
================================================
## Types

Custom Types provide a way to introduce reusable, sharable types to Apps.

### Defining a type

Types can be defined with the top level `DefineType` export. Below is an example
of setting up a custom Type used for incident management.

```ts
const IncidentType = DefineType({
  name: "incident",
  title: "Incident Ticket",
  description: "Use this to enter an Incident Ticket",
  type: Schema.types.object,
  properties: {
    id: {
      type: Schema.types.string,
      minLength: 3,
    },
    title: {
      type: Schema.types.string,
    },
    summary: {
      type: Schema.types.string,
    },
    severity: {
      type: Schema.types.string,
    },
    date_created: {
      type: Schema.types.number,
    },
  },
  required: [],
});
```

### Registering a type with the App

To register the newly defined type, add it to the array assigned to the `types`
parameter while defining the [`Manifest`][manifest].

Note: All Custom Types **must** be registered to the [Manifest][manifest] in
order for them to be used, but any types referenced by existing
[`functions`][functions], [`workflows`][workflows], [`datastores`][datastores],
or other types will be registered automatically.

```ts
Manifest({
  ...
  types: [IncidentType],
});
```

### Referencing types

To use a type as a [function][functions] parameter, set the parameter's `type`
property to the Type it should reference.

```js
input_parameters: {
  incident: : {
    title: 'A Special Incident',
    type: IncidentType
  }
   ...
}
```

_In the provided example the title from the Custom Type is being overridden_

[functions]: ./functions.md
[manifest]: ./manifest.md
[datastores]: ./datastores.md
[workflows]: ./workflows.md


================================================
FILE: docs/workflows.md
================================================
## Workflows

Workflows can be defined and included in your [manifest][manifest]. A workflow
itself has several pieces of metadata, such as a unique `callback_id`, a `title`
and a `description`. It can also include `input_parameters` just like a
[function][function]. Key to a workflow is a series of steps, each of which are
a function that can be passed dynamic data to their inputs through referencing
workflow inputs, or outputs from previous steps. Let's take a look at an
example.

```ts
import { DefineWorkflow, Manifest, Schema } from "slack-cloud-sdk/mod.ts";

const workflow = DefineWorkflow({
  callback_id: "my_workflow",
  title: "My Workflow",
  description: "A sample workflow",
  input_parameters: {
    properties: {
      a_string: {
        type: Schema.types.string,
      },
      a_channel: {
        type: Schema.slack.types.channel_id,
      }
    },
    required: ["a_string", "a_channel"],
  },
});

// register your workflow in your manifest
export default Manifest({
  ...,
  workflows: [
    workflow,
  ]
});
```

A workflow by itself isn't of much use, and isn't valid, until you add some
steps. Let's use the `DinoFunction` we've defined over in the
[functions][function] example as one of our steps. The `DinoFunction` has a
single `input_parameter` of `name` that we'll need to pass it. We'll use our
`a_string` workflow `input_parameter` as the value for this, but you could just
as easily pass a hard-coded value to any step input parameter as well.

```ts
import { DefineWorkflow } from "slack-cloud-sdk/mod.ts";
import { DinoFunction } from '../functions/dino.ts';

const workflow = DefineWorkflow({...});

const step1 = workflow.addStep(DinoFunction, {
  name: workflow.inputs.a_string,
});
```

Great, we've got a single step workflow that takes a string, and turns it into a
dinosaur name via our `DinoFunction`. It would be nice to see what that looks
like, so lets add another step that sends that value as a message somewhere. For
this, we can use one of Slack's functions. Notice how we can also use our
reference to `step1` to access an output called `dinoname` that the
`DinoFunction` produces.

```ts
const step1 = workflow.addStep(...);

workflow.addStep("slack#/functions/send_message", {
  channel: workflow.inputs.a_channel,
  message: `A dinosaur name: ${step1.outputs.dinoname}`,
});
```

You'll notice the first parameter to `addStep()` here is a string, instead of
something like our `DinoFunction`. This is because we're referencing a step
produced outside of our app, in this case by `slack`. We're using the string
reference of `"slack#/functions/send_message"` to identify the function we're
adding as a step. In fact, you can do the same thing with your own functions by
creating what's called a local reference string to your own app's function. This
uses your `callback_id`, and would look like `"#/functions/my_workflow"`. If we
added our function as a step that way, it would look like this:

```ts
const step1 = workflow.addStep("#/functions/my_workflow", {
  name: workflow.inputs.a_string,
});
```

The big difference here is you won't get some of the automatic typing of
`inputs` and `outputs` for that step, but you can still reference them as long
as you follow the definition of that function.

### Auto-Registration of workflow dependencies

When a workflow is registered on your `Manifest()` any `functions` it uses as
steps, or custom `types` used as `input_parameters` to the workflow or functions
it references are automatically registered in your manifest. This can save you
from having to register each function and type that a workflow might use, and
just register the workflow.

[manifest]: ./manifest.md
[function]: ./functions.md


================================================
FILE: scripts/build_npm.ts
================================================
// ex. scripts/build_npm.ts
import { build, emptyDir } from "jsr:@deno/dnt@0.42.1";

await emptyDir("./npm");

await build({
  typeCheck: false,
  test: false,
  entryPoints: ["./src/mod.ts"],
  outDir: "./npm",
  // ensures that the emitted package is compatible with node v14 later
  compilerOptions: {
    lib: ["ES2022.Error"], // fix ErrorOptions not exported in ES2020
    target: "ES2020",
  },
  shims: {
    // see JS docs for overview and more options
    deno: true,
    // Shim fetch, File, FormData, Headers, Request, and Response
    undici: true,
  },
  package: {
    // package.json properties
    name: "@slack/deno-slack-sdk",
    version: Deno.args[0],
    description: "Official library for using Deno Slack SDK in node Slack apps",
    license: "MIT",
    repository: {
      type: "git",
      url: "git+https://github.com/slackapi/deno-slack-sdk.git",
    },
    bugs: {
      url: "https://github.com/slackapi/deno-slack-sdk/issues",
    },
    // sets the minimum engine to node v14
    // as of writing, dnt transpilation-generated code
    // seems to only be able to successfully compile as far back ES2020
    engines: {
      "node": ">=14.20.1",
      "npm": ">=6.14.15",
    },
  },
});

// post build steps
Deno.copyFileSync("README.md", "npm/README.md");


================================================
FILE: scripts/bundle.ts
================================================
import * as esbuild from "npm:esbuild@0.25.5";
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader@0.11.1";
import { join } from "jsr:@std/path@1.1.0";

async function bundle(options: {
  target: string;
}): Promise<Uint8Array> {
  const cwd = Deno.cwd();
  try {
    // esbuild configuration options https://esbuild.github.io/api/#overview
    const result = await esbuild.build({
      entryPoints: [join(cwd, "src/mod.ts")],
      platform: "browser",
      target: options.target,
      format: "esm", // esm format stands for "ECMAScript module"
      bundle: true, // inline any imported dependencies into the file itself
      absWorkingDir: cwd, // root of this project
      write: false, // Favor returning the contents
      outdir: "out", // Nothing is being written to file here
      plugins: [
        ...denoPlugins({ configPath: join(cwd, "deno.jsonc") }),
      ],
    });
    return result.outputFiles[0].contents;
  } finally {
    esbuild.stop();
  }
}

console.log("Building bundle for target: deno1");
const deno1Bundle = await bundle({ target: "deno1" });
const deno1Content = new TextDecoder().decode(deno1Bundle);
console.log(`Bundle for deno1 built successfully!`);
console.log(deno1Content);

console.log("Building bundle for target: deno2");
const deno2Bundle = await bundle({ target: "deno2" });
const deno2Content = new TextDecoder().decode(deno2Bundle);
console.log(`Bundle for deno2 built successfully!`);
console.log(deno2Content);


================================================
FILE: scripts/imports/update.ts
================================================
import { parseArgs } from "jsr:@std/cli@1.0.17/parse-args";
import { dirname, join, relative } from "jsr:@std/path@1.1.0";

const flags = parseArgs(Deno.args, {
  string: ["import-file", "sdk"],
  default: {
    "import-file": `${Deno.cwd()}/deno.jsonc`,
    "sdk": "./deno-slack-sdk/",
  },
});

const importFilePath = await Deno.realPath(flags["import-file"]);
const importFileDir = dirname(importFilePath);
const sdkDir = await Deno.realPath(flags.sdk);

const importFileJsonIn = await Deno.readTextFile(importFilePath);
console.log(`content in ${importFilePath}:`, importFileJsonIn);

const sdkPackageSpecifier = join(
  relative(importFileDir, sdkDir),
  "/src/",
);

const importMap = JSON.parse(importFileJsonIn);
importMap["imports"]["deno-slack-sdk/"] = sdkPackageSpecifier;

const importMapJsonOut = JSON.stringify(importMap);
console.log("`import file` out content:", importMapJsonOut);
await Deno.writeTextFile(importFilePath, importMapJsonOut);


================================================
FILE: src/README.md
================================================
<h1 align="center">
  Deno Slack SDK
  <br>
</h1>

<p align="center">
    <img alt="deno.land version" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Flatest-version%2Fx%2Fdeno_slack_sdk%2Fmod.ts">
    <img alt="deno dependencies" src="https://img.shields.io/endpoint?url=https%3A%2F%2Fdeno-visualizer.danopia.net%2Fshields%2Fupdates%2Fx%2Fdeno_slack_sdk%2Fmod.ts">
    <img alt="Samples Integration Type-checking" src="https://github.com/slackapi/deno-slack-sdk/workflows/Samples%20Integration%20Type-checking/badge.svg">
  </a>
</p>

A Deno SDK to build Slack apps with the latest platform features. Read the
[quickstart guide](https://api.slack.com/automation/quickstart) and look at our
[code samples](https://api.slack.com/automation/samples) to learn how to build
apps.

## 📢 Important

We recommend importing this package from
[JSR](https://jsr.io/@slack/sdk/versions), but new releases are currently still
published to `deno.land/x` as well.

## Versioning

Releases for this repository follow the [SemVer](https://semver.org/) versioning
scheme. The SDK's contract is determined by the top-level exports from
`src/mod.ts` and `src/types.ts`. Exports not included in these files are deemed
internal and any modifications will not be treated as breaking changes. As such,
internal exports should be treated as unstable and used at your own risk.

## Setup

Make sure you have a development workspace where you have permission to install
apps. **Please note that the features in this project require that the workspace
be part of [a Slack paid plan](https://slack.com/pricing).**

### Install the Slack CLI

You need to install and configure the Slack CLI. Step-by-step instructions can
be found on our
[install & authorize page](https://api.slack.com/automation/cli/install).

## Creating an app

Create a blank project by executing the following command:

```zsh
slack create my-app --template slack-samples/deno-blank-template

cd my-app/
```

The `manifest.ts` file contains the app's configuration. This file defines
attributes like app name, description and functions.

### Create a [function](https://api.slack.com/automation/functions/custom)

```zsh
mkdir ./functions && touch ./functions/hello_world.ts
```

```ts
// Contents of ./functions/hello_world.ts
import { DefineFunction, Schema, SlackFunction } from "deno-slack-sdk/mod.ts";

export const HelloWorldFunctionDef = DefineFunction({
  callback_id: "hello_world_function",
  title: "Hello World",
  source_file: "functions/hello_world.ts",
  input_parameters: {
    properties: {},
    required: [],
  },
  output_parameters: {
    properties: {
      message: {
        type: Schema.types.string,
        description: "Hello world message",
      },
    },
    required: ["message"],
  },
});

export default SlackFunction(
  HelloWorldFunctionDef,
  () => {
    return {
      outputs: { message: "Hello World!" },
    };
  },
);
```

`DefineFunction` is used to define a custom function and provide Slack with the
information required to use it.

`SlackFunction` uses the definition returned by `DefineFunction` and your custom
executable code to export a Slack-usable custom function.

### Create a [workflow](https://api.slack.com/automation/workflows)

```zsh
mkdir ./workflows && touch ./workflows/hello_world.ts
```

```ts
// Contents of ./workflows/hello_world.ts
import { DefineWorkflow, Schema } from "deno-slack-sdk/mod.ts";
import { HelloWorldFunctionDef } from "../functions/hello_world.ts";

const HelloWorldWorkflowDef = DefineWorkflow({
  callback_id: "hello_world_workflow",
  title: "Hello World Workflow",
  input_parameters: {
    properties: {
      channel: {
        type: Schema.slack.types.channel_id,
      },
    },
    required: ["channel"],
  },
});

const helloWorldStep = HelloWorldWorkflowDef.addStep(HelloWorldFunctionDef, {});

HelloWorldWorkflowDef.addStep(Schema.slack.functions.SendMessage, {
  channel_id: HelloWorldWorkflowDef.inputs.channel,
  message: helloWorldStep.outputs.message,
});

export default HelloWorldWorkflowDef;
```

`DefineWorkflow` is used to define a workflow and provide Slack with the
information required to use it.

`HelloWorldWorkflow.addStep` is used to add a step to the workflow; here we add
the `HelloWorldFunction` and then the `SendMessage` Slack Function that will
post the `message` to a Slack channel.

### Update the [manifest](https://api.slack.com/automation/manifest)

```ts
// Contents of manifest.ts
import { Manifest } from "deno-slack-sdk/mod.ts";
import HelloWorldWorkflow from "./workflows/hello_world.ts";

export default Manifest({
  name: "my-app",
  description: "A Hello World app",
  icon: "assets/default_new_app_icon.png",
  workflows: [HelloWorldWorkflow],
  outgoingDomains: [],
  botScopes: ["chat:write", "chat:write.public"],
});
```

`Manifest` is used to define your apps
[manifest](https://api.slack.com/automation/manifest) and provides Slack with
the information required to manage it.

### Create a [trigger](https://api.slack.com/automation/triggers)

```zsh
mkdir ./triggers && touch ./triggers/hello_world.ts
```

```ts
// Contents of ./triggers/hello_world.ts
import { Trigger } from "deno-slack-sdk/types.ts";
import { TriggerContextData, TriggerTypes } from "deno-slack-api/mod.ts";
import HelloWorldWorkflow from "../workflows/hello_world.ts";

const trigger: Trigger<typeof HelloWorldWorkflow.definition> = {
  type: TriggerTypes.Shortcut,
  name: "Reverse a string",
  description: "Starts the workflow to reverse a string",
  workflow: `#/workflows/${HelloWorldWorkflow.definition.callback_id}`,
  inputs: {
    channel: {
      value: TriggerContextData.Shortcut.channel_id,
    },
  },
};

export default trigger;
```

The `Trigger` object is used to define a trigger that will invoke the
`HelloWorldWorkflow`. The Slack CLI will detect this file and prompt you for its
creation.

## Running an app

```zsh
slack run
```

When prompted, create the `triggers/hello_world.ts` trigger. This will send your
trigger configuration to Slack.

Post the `Hello world shortcut trigger` in a slack message and **use it**

## Getting Help

[This documentation](https://api.slack.com/automation) has more information on
basic and advanced concepts of the SDK.

Information on how to get started developing with Deno can be found in
[this documentation](https://api.slack.com/automation/deno/develop).

If you get stuck, we're here to help. The following are the best ways to get
assistance working through your issue:

- [Issue Tracker](https://github.com/slackapi/deno-slack-sdk/issues?q=is%3Aissue)
  for questions, bug reports, feature requests, and general discussion. **Try
  searching for an existing issue before creating a new one.**
- Email our developer support team: `support@slack.com`

## Contributing

Contributions are more than welcome. Please look at the
[contributing guidelines](https://github.com/slackapi/deno-slack-sdk/blob/main/.github/CONTRIBUTING.md)
for more info!


================================================
FILE: src/datastore/datastore_test.ts
================================================
import { assertStrictEquals } from "../dev_deps.ts";
import { DefineDatastore } from "./mod.ts";
import SchemaTypes from "../schema/schema_types.ts";
import { DefineType } from "../types/mod.ts";

const customType = DefineType({
  name: "custom_type",
  type: SchemaTypes.boolean,
});

Deno.test("Datastore sets appropriate defaults", () => {
  const datastore = DefineDatastore({
    name: "dinos",
    primary_key: "attr1",
    attributes: {
      attr1: {
        type: SchemaTypes.string,
      },
      attr2: {
        type: SchemaTypes.boolean,
      },
      attr3: {
        type: customType,
      },
      attr4: {
        type: SchemaTypes.object,
        properties: {
          anObjectString: { type: SchemaTypes.string },
        },
      },
    },
  });
  assertStrictEquals(datastore.definition.name, "dinos");
  assertStrictEquals(datastore.definition.primary_key, "attr1");

  const exported = datastore.export();
  assertStrictEquals(exported.primary_key, "attr1");
  assertStrictEquals(exported.attributes.attr1.type, SchemaTypes.string);
  assertStrictEquals(exported.attributes.attr2.type, SchemaTypes.boolean);
  assertStrictEquals(exported.attributes.attr3.type, customType);
  assertStrictEquals(exported.attributes.attr4.type, SchemaTypes.object);
  assertStrictEquals(
    exported.attributes.attr4.properties?.anObjectString?.type,
    SchemaTypes.string,
  );
});


================================================
FILE: src/datastore/mod.ts
================================================
import type { SlackManifest } from "../manifest/mod.ts";
import type { ManifestDatastoreSchema } from "../manifest/manifest_schema.ts";
import type {
  ISlackDatastore,
  SlackDatastoreAttributes,
  SlackDatastoreDefinition,
} from "./types.ts";
import { isCustomType } from "../types/mod.ts";

/**
 * Define a datastore and primary key and attributes for use in a Slack application.
 * @param {SlackDatastoreDefinition<string, SlackDatastoreAttributes, string>} definition Defines information about your datastore.
 * @returns {SlackDatastore}
 */
export const DefineDatastore = <
  Name extends string,
  Attributes extends SlackDatastoreAttributes,
  PrimaryKey extends keyof Attributes,
  TimeToLiveAttribute extends keyof Attributes,
>(
  definition: SlackDatastoreDefinition<
    Name,
    Attributes,
    PrimaryKey,
    TimeToLiveAttribute
  >,
) => {
  return new SlackDatastore(definition);
};

export class SlackDatastore<
  Name extends string,
  Attributes extends SlackDatastoreAttributes,
  PrimaryKey extends keyof Attributes,
  TimeToLiveAttribute extends keyof Attributes,
> implements ISlackDatastore {
  public name: Name;

  constructor(
    public definition: SlackDatastoreDefinition<
      Name,
      Attributes,
      PrimaryKey,
      TimeToLiveAttribute
    >,
  ) {
    this.name = definition.name;
  }

  registerAttributeTypes(manifest: SlackManifest) {
    Object.values(this.definition.attributes).forEach((attribute) => {
      if (isCustomType(attribute.type)) {
        manifest.registerType(attribute.type);
      }
    });
  }

  export(): ManifestDatastoreSchema {
    return {
      primary_key: this.definition.primary_key as string,
      time_to_live_attribute: this.definition.time_to_live_attribute as string,
      attributes: this.definition.attributes,
    };
  }
}


================================================
FILE: src/datastore/types.ts
================================================
import type { ICustomType } from "../types/types.ts";
import type { ManifestDatastoreSchema } from "../manifest/manifest_schema.ts";
import type { SlackManifest } from "../manifest/mod.ts";
import type { SlackPrimitiveTypes } from "../schema/slack/types/mod.ts";
import type { ValidSchemaTypes } from "../schema/schema_types.ts";
import type { ValidSlackPrimitiveTypes } from "../schema/slack/types/mod.ts";
import type { LooseStringAutocomplete } from "../type_utils.ts";

type InvalidDatastoreTypes =
  | typeof SlackPrimitiveTypes.blocks
  | typeof SlackPrimitiveTypes.oauth2;

type ValidDatastoreTypes = Exclude<
  | ValidSchemaTypes
  | ValidSlackPrimitiveTypes,
  InvalidDatastoreTypes
>;

export type SlackDatastoreAttribute = {
  // supports custom types, primitive types, inline objects and lists
  type: LooseStringAutocomplete<ValidDatastoreTypes> | ICustomType;
};

export type SlackDatastoreAttributes = Record<string, SlackDatastoreAttribute>;

export type SlackDatastoreDefinition<
  Name extends string,
  Attributes extends SlackDatastoreAttributes,
  PrimaryKey extends keyof Attributes,
  TimeToLiveAttribute extends keyof Attributes,
> = {
  name: Name;
  "primary_key": PrimaryKey;
  "time_to_live_attribute"?: TimeToLiveAttribute;
  attributes: Attributes;
};

export interface ISlackDatastore {
  name: string;
  export: () => ManifestDatastoreSchema;
  registerAttributeTypes: (manifest: SlackManifest) => void;
}

export type SlackDatastoreItem<Attributes extends SlackDatastoreAttributes> = {
  // TODO: In the future, see if we can map the attribute.type to
  // the TS type map like functions do w/ parameters
  // deno-lint-ignore no-explicit-any
  [k in keyof Attributes]: any;
};

export type PartialSlackDatastoreItem<
  Attributes extends SlackDatastoreAttributes,
> = OptionalPartial<Attributes>;

// deno-lint-ignore no-explicit-any
type OptionalPartial<T extends any> = {
  // deno-lint-ignore no-explicit-any
  [P in keyof T]?: any;
};


================================================
FILE: src/deps.ts
================================================
export { SlackAPI } from "jsr:@slack/api@2.9.0";
export type { SlackAPIClient, Trigger } from "jsr:@slack/api@2.9.0/types";


================================================
FILE: src/dev_deps.ts
================================================
export {
  assertEquals,
  assertExists,
  assertInstanceOf,
  AssertionError,
  assertMatch,
  assertNotStrictEquals,
  assertRejects,
  assertStrictEquals,
  assertStringIncludes,
  fail,
} from "jsr:@std/assert@^1.0.0";
export * as mock from "jsr:@std/testing@^1.0.0/mock";

export { assertType as assert } from "jsr:@std/testing@^1.0.0/types";
export type { IsAny, IsExact } from "jsr:@std/testing@^1.0.0/types";

export { toPascalCase } from "jsr:@std/text@^1.0.0";


================================================
FILE: src/events/events_test.ts
================================================
import { DefineEvent } from "./mod.ts";
import { assertEquals } from "../dev_deps.ts";
import { DefineType, Schema } from "../mod.ts";
import { isCustomType } from "../types/mod.ts";

Deno.test("DefineEvent accepts object types", () => {
  const TestEvent = DefineEvent({
    name: "test",
    type: Schema.types.object,
    properties: {},
  });

  assertEquals(TestEvent.id, TestEvent.definition.name);
  assertEquals(typeof TestEvent.definition.type, "string");
});

Deno.test("DefineEvent accepts custom types", () => {
  const TestType = DefineType({
    name: "test",
    type: Schema.types.object,
    properties: {},
  });

  const TestEvent = DefineEvent({
    title: "Title",
    description: "Description",
    name: "test",
    type: TestType,
  });

  assertEquals(isCustomType(TestEvent.definition.type), true);
});

Deno.test("DefineEvent is properly stringified", () => {
  const TestEvent = DefineEvent({
    name: "test",
    type: Schema.types.object,
    properties: {},
  });

  assertEquals(`${TestEvent}`, TestEvent.id);
  assertEquals(TestEvent.toJSON(), TestEvent.id);
  assertEquals(TestEvent.toString(), TestEvent.id);
});


================================================
FILE: src/events/mod.ts
================================================
import type { SlackManifest } from "../manifest/mod.ts";
import type { ManifestCustomEventSchema } from "../manifest/manifest_schema.ts";
import type {
  CustomEventDefinition,
  DefineEventSignature,
  ICustomEvent,
} from "./types.ts";
import { isCustomType } from "../types/mod.ts";
import { isTypedObject } from "../parameters/mod.ts";

export const DefineEvent: DefineEventSignature = <
  Def extends CustomEventDefinition,
>(
  definition: Def,
) => {
  return new CustomEvent(definition);
};

export class CustomEvent<Def extends CustomEventDefinition>
  implements ICustomEvent {
  public id: string;
  public title: string | undefined;
  public description: string | undefined;

  constructor(
    public definition: Def,
  ) {
    this.id = definition.name;
    this.definition = definition;
    this.description = definition.description;
    this.title = definition.title;
  }

  private generateReferenceString() {
    return this.id;
  }

  toString() {
    return this.generateReferenceString();
  }

  toJSON() {
    return this.generateReferenceString();
  }

  registerParameterTypes(manifest: SlackManifest) {
    if (isCustomType(this.definition.type)) {
      manifest.registerType(this.definition.type);
    } else if (isTypedObject(this.definition)) {
      Object.values(this.definition.properties)?.forEach((property) => {
        if (isCustomType(property.type)) {
          manifest.registerType(property.type);
        }
      });
    }
  }
  export(): ManifestCustomEventSchema {
    // remove name from the definition we pass to the manifest
    const { name: _n, ...definition } = this.definition;
    // Using JSON.stringify to force any custom types into their string reference
    return JSON.parse(JSON.stringify(definition));
  }
}


================================================
FILE: src/events/types.ts
================================================
import type {
  CustomTypeParameterDefinition,
  TypedObjectParameter,
} from "../parameters/definition_types.ts";
import type { ManifestCustomEventSchema } from "../manifest/manifest_schema.ts";
import type { CustomEvent } from "./mod.ts";
import type { SlackManifest } from "../manifest/mod.ts";

type AcceptedEventTypes =
  | TypedObjectParameter
  | CustomTypeParameterDefinition;

export type CustomEventDefinition =
  & {
    /**
     * The name of your event
     * @example my_custom_event
     */
    name: string;
  }
  & AcceptedEventTypes;

export type DefineEventSignature = {
  <Def extends CustomEventDefinition>(definition: Def): CustomEvent<Def>;
};

export interface ICustomEvent {
  id: string;
  definition: CustomEventDefinition;
  description?: string;
  registerParameterTypes: (manifest: SlackManifest) => void;
  export(): ManifestCustomEventSchema;
}


================================================
FILE: src/functions/definitions/connector-function.ts
================================================
import type { ManifestFunctionType } from "../../manifest/manifest_schema.ts";
import type {
  ParameterSetDefinition,
  PossibleParameterKeys,
} from "../../parameters/types.ts";
import type {
  FunctionDefinitionArgs,
  ISlackFunctionDefinition,
} from "../types.ts";

/**
 * Define a connector and its input and output parameters for use in a Slack application.
 * @param {FunctionDefinitionArgs<InputParameters, OutputParameters, RequiredInput, RequiredOutput>} definition Defines information about your function (title, description) as well as formalizes the input and output parameters of a connector
 * @returns {ConnectorFunctionDefinition}
 */
export const DefineConnector = <
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
>(
  definition: FunctionDefinitionArgs<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  >,
) => {
  return new ConnectorFunctionDefinition(definition);
};

export class ConnectorFunctionDefinition<
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
> implements
  ISlackFunctionDefinition<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  > {
  type: ManifestFunctionType = "API";
  id: string;

  constructor(
    public definition: FunctionDefinitionArgs<
      InputParameters,
      OutputParameters,
      RequiredInput,
      RequiredOutput
    >,
  ) {
    this.id = definition.callback_id;
    this.definition = definition;
  }
}


================================================
FILE: src/functions/definitions/connector-function_test.ts
================================================
import {
  assertEquals,
  assertInstanceOf,
  assertStrictEquals,
} from "../../dev_deps.ts";
import type { PossibleParameterKeys } from "../../parameters/types.ts";
import Schema from "../../schema/mod.ts";
import {
  ConnectorFunctionDefinition,
  DefineConnector,
} from "./connector-function.ts";

type emptyParameterType = Record<string, never>;

Deno.test("DefineConnector returns an instance of `ConnectorFunctionDefinition`", () => {
  const connector = DefineConnector({
    callback_id: "my_connector",
    title: "My Connector",
  });

  assertInstanceOf(
    connector,
    ConnectorFunctionDefinition<
      emptyParameterType,
      emptyParameterType,
      PossibleParameterKeys<emptyParameterType>,
      PossibleParameterKeys<emptyParameterType>
    >,
  );
});

Deno.test("DefineConnector sets appropriate defaults", () => {
  const Func = DefineConnector({
    callback_id: "my_connector",
    title: "My Connector",
  });

  assertEquals(Func.id, Func.definition.callback_id);
  assertStrictEquals(Func.type, "API");
  assertEquals(Func.definition.title, "My Connector");
  assertEquals(Func.definition.input_parameters, undefined);
  assertEquals(Func.definition.output_parameters, undefined);
});

Deno.test("DefineConnector with input parameters but no output parameters", () => {
  const inputParameters = {
    properties: {
      aString: { type: Schema.types.string },
    },
    required: [],
  };
  const NoOutputParamFunction = DefineConnector({
    callback_id: "input_params_only",
    title: "No Parameter Function",
    input_parameters: inputParameters,
  });

  assertStrictEquals(
    NoOutputParamFunction.definition.input_parameters,
    inputParameters,
  );
  assertEquals(
    NoOutputParamFunction.definition.output_parameters,
    undefined,
  );
});

Deno.test("DefineConnector with output parameters but no input parameters", () => {
  const outputParameters = {
    properties: {
      aString: { type: Schema.types.string },
    },
    required: [],
  };
  const NoInputParamFunction = DefineConnector({
    callback_id: "output_params_only",
    title: "No Parameter Function",
    output_parameters: outputParameters,
  });

  assertEquals(
    NoInputParamFunction.definition.input_parameters,
    undefined,
  );
  assertStrictEquals(
    NoInputParamFunction.definition.output_parameters,
    outputParameters,
  );
});


================================================
FILE: src/functions/definitions/mod.ts
================================================
export { DefineFunction, SlackFunctionDefinition } from "./slack-function.ts";
export {
  ConnectorFunctionDefinition,
  DefineConnector,
} from "./connector-function.ts";


================================================
FILE: src/functions/definitions/slack-function.ts
================================================
import type {
  ManifestFunctionSchema,
  ManifestFunctionType,
} from "../../manifest/manifest_schema.ts";
import type { SlackManifest } from "../../mod.ts";
import type {
  ParameterSetDefinition,
  PossibleParameterKeys,
} from "../../parameters/types.ts";
import type {
  ISlackFunctionDefinition,
  SlackFunctionDefinitionArgs,
} from "../types.ts";

/**
 * Define a function and its input and output parameters for use in a Slack application.
 * @param {SlackFunctionDefinitionArgs<InputParameters, OutputParameters, RequiredInput, RequiredOutput>} definition Defines information about your function (title, description) as well as formalizes the input and output parameters of your function
 * @returns {SlackFunctionDefinition}
 */
export const DefineFunction = <
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
>(
  definition: SlackFunctionDefinitionArgs<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  >,
) => {
  return new SlackFunctionDefinition(definition);
};

export class SlackFunctionDefinition<
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
> implements
  ISlackFunctionDefinition<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  > {
  type: ManifestFunctionType = "app";
  id: string;

  constructor(
    public definition: SlackFunctionDefinitionArgs<
      InputParameters,
      OutputParameters,
      RequiredInput,
      RequiredOutput
    >,
  ) {
    this.id = definition.callback_id;
    this.definition = definition;
  }

  registerParameterTypes(manifest: SlackManifest) {
    const { input_parameters: inputParams, output_parameters: outputParams } =
      this.definition;
    manifest.registerTypes(inputParams?.properties ?? {});
    manifest.registerTypes(outputParams?.properties ?? {});
  }

  export(): ManifestFunctionSchema {
    return {
      title: this.definition.title,
      description: this.definition.description,
      source_file: this.definition.source_file,
      input_parameters: this.definition.input_parameters ??
        { properties: {}, required: [] },
      output_parameters: this.definition.output_parameters ??
        { properties: {}, required: [] },
    };
  }
}

export function isCustomFunctionDefinition<
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutputs extends PossibleParameterKeys<OutputParameters>,
>(
  functionDefinition: ISlackFunctionDefinition<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutputs
  >,
): functionDefinition is SlackFunctionDefinition<
  InputParameters,
  OutputParameters,
  RequiredInput,
  RequiredOutputs
> {
  if (
    functionDefinition.type === "app" &&
    functionDefinition.export &&
    functionDefinition.registerParameterTypes
  ) {
    return true;
  }
  return false;
}


================================================
FILE: src/functions/definitions/slack-function_test.ts
================================================
import {
  assertEquals,
  assertInstanceOf,
  assertStrictEquals,
} from "../../dev_deps.ts";
import Schema from "../../schema/mod.ts";
import type { PossibleParameterKeys } from "../../parameters/types.ts";
import {
  DefineFunction,
  isCustomFunctionDefinition,
  SlackFunctionDefinition,
} from "./slack-function.ts";
import type { ISlackFunctionDefinition } from "../types.ts";

// TODO: Re-add tests to validate function execution when we've determined how to execute functions locally

const emptyParameterObject = Object.freeze({ required: [], properties: {} });
type emptyParameterType = Record<string, never>;

Deno.test("DefineFunction returns an instance of `SlackFunctionDefinition`", () => {
  const func = DefineFunction({
    callback_id: "my_function",
    title: "My function",
    source_file: "functions/dino.ts",
  });

  assertInstanceOf(
    func,
    SlackFunctionDefinition<
      emptyParameterType,
      emptyParameterType,
      PossibleParameterKeys<emptyParameterType>,
      PossibleParameterKeys<emptyParameterType>
    >,
  );
});

Deno.test("DefineFunction sets appropriate defaults", () => {
  const Func = DefineFunction({
    callback_id: "my_function",
    title: "My function",
    source_file: "functions/dino.ts",
  });

  assertEquals(Func.id, Func.definition.callback_id);
  assertEquals(Func.definition.title, "My function");
  assertEquals(Func.definition.source_file, "functions/dino.ts");

  const exportedFunc = Func.export();
  assertStrictEquals(exportedFunc.source_file, "functions/dino.ts");
  assertEquals(exportedFunc.input_parameters, emptyParameterObject);
  assertEquals(exportedFunc.output_parameters, emptyParameterObject);
});

Deno.test("DefineFunction with required params", () => {
  const AllTypesFunction = DefineFunction({
    callback_id: "my_function",
    title: "All Types Function",
    source_file: "functions/example.ts",
    input_parameters: {
      properties: {
        myString: {
          type: Schema.types.string,
          title: "My string",
          description: "a really neat value",
          hint: "Ex. my neat value",
        },
        myBoolean: {
          type: Schema.types.boolean,
          title: "My boolean",
          hint: "Ex: true/false",
        },
        myInteger: {
          type: Schema.types.integer,
          description: "integer",
          hint: "0-100",
        },
        myNumber: {
          type: Schema.types.number,
          description: "number",
        },
      },
      required: ["myString", "myNumber"],
    },
    output_parameters: {
      properties: {
        out: {
          type: Schema.types.string,
        },
      },
      required: ["out"],
    },
  });

  assertEquals(AllTypesFunction.definition.input_parameters?.required, [
    "myString",
    "myNumber",
  ]);
  assertEquals(AllTypesFunction.definition.output_parameters?.required, [
    "out",
  ]);
  assertEquals(
    AllTypesFunction.definition.input_parameters?.properties.myString.hint,
    "Ex. my neat value",
  );
  assertEquals(
    AllTypesFunction.definition.input_parameters?.properties.myBoolean.hint,
    "Ex: true/false",
  );
});

Deno.test("DefineFunction without input and output parameters", () => {
  const NoParamFunction = DefineFunction({
    callback_id: "no_params",
    title: "No Parameter Function",
    source_file: "functions/no_params.ts",
  });

  assertEquals(emptyParameterObject, NoParamFunction.export().input_parameters);
  assertEquals(
    emptyParameterObject,
    NoParamFunction.export().output_parameters,
  );
});

Deno.test("DefineFunction with input parameters but no output parameters", () => {
  const inputParameters = {
    properties: {
      aString: { type: Schema.types.string },
    },
    required: [],
  };
  const NoOutputParamFunction = DefineFunction({
    callback_id: "input_params_only",
    title: "No Parameter Function",
    source_file: "functions/input_params_only.ts",
    input_parameters: inputParameters,
  });

  NoOutputParamFunction.export();

  assertStrictEquals(
    inputParameters,
    NoOutputParamFunction.definition.input_parameters,
  );
  assertEquals(
    emptyParameterObject,
    NoOutputParamFunction.export().output_parameters,
  );
});

Deno.test("DefineFunction with output parameters but no input parameters", () => {
  const outputParameters = {
    properties: {
      aString: { type: Schema.types.string },
    },
    required: [],
  };
  const NoInputParamFunction = DefineFunction({
    callback_id: "output_params_only",
    title: "No Parameter Function",
    source_file: "functions/output_params_only.ts",
    output_parameters: outputParameters,
  });

  assertEquals(
    emptyParameterObject,
    NoInputParamFunction.export().input_parameters,
  );
  assertStrictEquals(
    outputParameters,
    NoInputParamFunction.definition.output_parameters,
  );
});

Deno.test("DefineFunction using an OAuth2 property requests a provider key", () => {
  /**
   * The `oauth2_provider_key` is not currently required because `type` supports any string
   * But eventually we'd like to support static errors for OAuth2 properties without the provider key
   */

  const OAuth2Function = DefineFunction({
    callback_id: "oauth",
    title: "OAuth Function",
    source_file: "functions/oauth.ts",
    input_parameters: {
      properties: {
        googleAccessTokenId: {
          type: Schema.slack.types.oauth2,
          oauth2_provider_key: "test",
        },
      },
      required: [],
    },
  });

  /**
   * TODO: Support the following test for static error
    // ts-expect-error `oauth2_provider_key` must be set
    const _IncompleteOAuth2Function = DefineFunction({
      callback_id: "oauth",
      title: "OAuth Function",
      source_file: "functions/oauth.ts",
      input_parameters: {
        properties: {
          googleAccessTokenId: {
            type: Schema.slack.types.oauth2,
          },
        },
        required: [],
      },
    });
   */

  assertEquals(
    {
      googleAccessTokenId: {
        oauth2_provider_key: "test",
        type: Schema.slack.types.oauth2,
      },
    },
    OAuth2Function.export().input_parameters.properties,
  );
});

Deno.test("DefineFunction using an OAuth2 property allows require_end_user_auth", () => {
  const OAuth2Function = DefineFunction({
    callback_id: "oauth",
    title: "OAuth Function",
    source_file: "functions/oauth.ts",
    input_parameters: {
      properties: {
        googleAccessTokenId: {
          type: Schema.slack.types.oauth2,
          oauth2_provider_key: "test",
          require_end_user_auth: true,
        },
      },
      required: [],
    },
  });

  assertEquals(
    {
      googleAccessTokenId: {
        oauth2_provider_key: "test",
        type: Schema.slack.types.oauth2,
        require_end_user_auth: true,
      },
    },
    OAuth2Function.export().input_parameters.properties,
  );
});

Deno.test("isCustomFunctionDefinition should return true when SlackFunctionDefinition is passed", () => {
  const NoParamFunction = DefineFunction({
    callback_id: "no_params",
    title: "No Parameter Function",
    source_file: "functions/no_params.ts",
  });

  assertInstanceOf(NoParamFunction, SlackFunctionDefinition);
  assertEquals(true, isCustomFunctionDefinition(NoParamFunction));
});

Deno.test("isCustomFunctionDefinition should return false when a non custom function is passed", () => {
  const notCustomFunction: ISlackFunctionDefinition<
    emptyParameterType,
    emptyParameterType,
    PossibleParameterKeys<emptyParameterType>,
    PossibleParameterKeys<emptyParameterType>
  > = {
    type: "API",
    id: "not_custom",
    definition: {
      callback_id: "not_custom",
      title: "Not a custom Function",
      description: undefined,
      input_parameters: undefined,
      output_parameters: undefined,
    },
  };
  assertEquals(false, isCustomFunctionDefinition(notCustomFunction));
});


================================================
FILE: src/functions/enrich-context.ts
================================================
import { SlackAPI } from "../deps.ts";
import type {
  BaseRuntimeFunctionContext,
  FunctionContextEnrichment,
} from "./types.ts";

export const enrichContext = (
  // deno-lint-ignore no-explicit-any
  context: BaseRuntimeFunctionContext<any>,
): 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,
  };
};


================================================
FILE: src/functions/enrich-context_test.ts
================================================
import { assertExists } from "../dev_deps.ts";
import { enrichContext } from "./enrich-context.ts";
import type { BaseRuntimeFunctionContext } from "./types.ts";

Deno.test("enrichContext with no env.SLACK_API_URL", () => {
  // deno-lint-ignore no-explicit-any
  const ctx: BaseRuntimeFunctionContext<any> = {
    env: {},
    inputs: {},
    team_id: "team",
    enterprise_id: "",
    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<any> = {
    env: {
      "SLACK_API_URL": "https://something.slack.com/api",
    },
    inputs: {},
    team_id: "team",
    enterprise_id: "",
    token: "token",
  };

  const newContext = enrichContext(ctx);

  assertExists(newContext.client);
});


================================================
FILE: src/functions/interactivity/block_actions_router.ts
================================================
import type {
  ParameterSetDefinition,
  PossibleParameterKeys,
} from "../../parameters/types.ts";
import type { SlackFunctionDefinition } from "../definitions/mod.ts";
import { UnhandledEventError } from "../unhandled-event-error.ts";
import { enrichContext } from "../enrich-context.ts";
import type {
  FunctionDefinitionArgs,
  FunctionRuntimeParameters,
} from "../types.ts";
import type {
  ActionContext,
  BlockActionConstraint,
  BlockActionHandler,
  RuntimeActionContext,
} from "./types.ts";
import type { BlockAction } from "./block_kit_types.ts";
import {
  matchBasicConstraintField,
  normalizeConstraintToArray,
} from "./matchers.ts";

/**
 * Define an actions "router" and its input and output parameters for use in a Slack application. The ActionsRouter will route incoming action events to action-specific handlers.
 * @param {SlackFunctionDefinition<InputParameters, OutputParameters, RequiredInput, RequiredOutput>} func Reference to your previously-created SlackFunction, defined via DefineFunction
 * @returns {ActionsRouter}
 */
export const BlockActionsRouter = <
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
>(
  func: SlackFunctionDefinition<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  >,
) => {
  const router = new ActionsRouter(func);

  // deno-lint-ignore no-explicit-any
  const exportedHandler: any = router.export();

  // deno-lint-ignore no-explicit-any
  exportedHandler.addHandler = ((...args: any) => {
    router.addHandler.apply(router, args);

    return exportedHandler;
  }) as typeof router.addHandler;

  return exportedHandler as
    & ReturnType<typeof router.export>
    & Pick<typeof router, "addHandler">;
};

export class ActionsRouter<
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
> {
  private routes: Array<
    [BlockActionConstraint, BlockActionHandler<typeof this.func.definition>]
  >;

  constructor(
    private func: SlackFunctionDefinition<
      InputParameters,
      OutputParameters,
      RequiredInput,
      RequiredOutput
    >,
  ) {
    this.func = func;
    this.routes = [];
  }

  /**
   * Add an action handler for something that can match an action event.
   * @param {BlockActionConstraint} actionConstraint A BlockActionConstraintField(i.e. a string, array of strings or regular expression) or more complex BlockActionConstraintObject to match incoming block action events. A BlockActionConstraintField parameter are matched with a block action event's `action_id` property. A BlockActionConstraintObject parameter allows to match with other block action event properties like `block_id` as well as `action_id`. If multiple properties are specified using BlockActionConstraintObject, then the event must match ALL provided BlockActionConstraintObject properties.
   * @returns {ActionsRouter}
   */
  addHandler(
    actionConstraint: BlockActionConstraint,
    handler: BlockActionHandler<
      FunctionDefinitionArgs<
        InputParameters,
        OutputParameters,
        RequiredInput,
        RequiredOutput
      >
    >,
  ): ActionsRouter<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  > {
    this.routes.push([actionConstraint, handler]);
    return this;
  }

  /**
   * 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() {
    return async (
      context: RuntimeActionContext<
        FunctionRuntimeParameters<InputParameters, RequiredInput>
      >,
    ) => {
      const action: BlockAction = context.action;
      const handler = this.matchHandler(action);
      if (handler === null) {
        throw new UnhandledEventError(
          `Received block action payload with action=${
            JSON.stringify(action)
          } but this app has no action handler defined to handle it!`,
        );
      }

      const enrichedContext = enrichContext(context) as ActionContext<
        FunctionRuntimeParameters<InputParameters, RequiredInput>
      >;

      return await handler(enrichedContext);
    };
  }

  /**
   * Return the first registered ActionHandler that matches the action ID string provided.
   */
  matchHandler(
    action: BlockAction,
  ):
    | BlockActionHandler<
      FunctionDefinitionArgs<
        InputParameters,
        OutputParameters,
        RequiredInput,
        RequiredOutput
      >
    >
    | null {
    for (let i = 0; i < this.routes.length; i++) {
      const route = this.routes[i];
      let [constraint, handler] = route;
      // Handle different constraint types below
      if (
        constraint instanceof RegExp || constraint instanceof Array ||
        typeof constraint === "string"
      ) {
        // Normalize simple string constraints to be an array of strings for consistency in handling inside this method.
        constraint = normalizeConstraintToArray(constraint);
        // Handle the case where the constraint is either a regex or an array of strings to match against action_id
        if (matchBasicConstraintField(constraint, "action_id", action)) {
          return handler;
        }
      } else {
        // Assumes an object as a constraint (type BlockActionConstraintObject)
        // Return first match *within* any of the defined fields on the constaint object, but ensure there is a match on *all* defined fields
        // Effectively a logical AND across the action_id and block_id field(s)
        // If either of the constraint fields are not defined, pre-set them to have matched so we can effectively
        // ignore them when determining if we have a match by &&'ing them
        let actionIDMatched = constraint.action_id ? false : true;
        let blockIDMatched = constraint.block_id ? false : true;
        if (constraint.action_id) {
          actionIDMatched = matchBasicConstraintField(
            normalizeConstraintToArray(constraint.action_id),
            "action_id",
            action,
          );
        }
        if (constraint.block_id) {
          blockIDMatched = matchBasicConstraintField(
            normalizeConstraintToArray(constraint.block_id),
            "block_id",
            action,
          );
        }
        if (blockIDMatched && actionIDMatched) {
          return handler;
        }
      }
    }
    return null;
  }
}


================================================
FILE: src/functions/interactivity/block_actions_router_test.ts
================================================
import { SlackAPI } from "../../deps.ts";
import {
  assertEquals,
  assertExists,
  assertRejects,
  mock,
} from "../../dev_deps.ts";
import { BlockActionsRouter } from "./block_actions_router.ts";
import type { ActionContext } from "./types.ts";
import type { BlockAction } from "./block_kit_types.ts";
import type {
  FunctionParameters,
  FunctionRuntimeParameters,
} from "../types.ts";
import type {
  ParameterSetDefinition,
  PossibleParameterKeys,
} from "../../parameters/types.ts";
import type { SlackFunctionDefinition } from "../definitions/mod.ts";
import { DefineFunction, Schema } from "../../mod.ts";

// Helper test types
// TODO: maybe we want to export this for userland usage at some point?
// Very much a direct copy from the existing main function tester types and utilties in src/functions/tester
type SlackActionHandlerTesterArgs<InputParameters extends FunctionParameters> =
  & Partial<
    ActionContext<InputParameters>
  >
  & {
    inputs: InputParameters;
  };

type CreateActionHandlerContext<
  InputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
> = {
  (
    args: SlackActionHandlerTesterArgs<
      FunctionRuntimeParameters<InputParameters, RequiredInput>
    >,
  ): ActionContext<
    FunctionRuntimeParameters<InputParameters, RequiredInput>
  >;
};

type SlackActionHandlerTesterResponse<
  InputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
> = {
  createContext: CreateActionHandlerContext<InputParameters, RequiredInput>;
};

type SlackActionHandlerTesterSignature = {
  <
    InputParameters extends ParameterSetDefinition,
    OutputParameters extends ParameterSetDefinition,
    RequiredInput extends PossibleParameterKeys<InputParameters>,
    RequiredOutput extends PossibleParameterKeys<OutputParameters>,
  >(
    func: SlackFunctionDefinition<
      InputParameters,
      OutputParameters,
      RequiredInput,
      RequiredOutput
    >,
  ): SlackActionHandlerTesterResponse<
    InputParameters,
    RequiredInput
  >;
};

// Helper test fixtures and utilities
const DEFAULT_ACTION: BlockAction = {
  type: "button",
  block_id: "block_id",
  action_ts: `${new Date().getTime()}`,
  action_id: "action_id",
  text: { type: "plain_text", text: "duncare", emoji: false },
  style: "danger",
};
const SlackActionHandlerTester: SlackActionHandlerTesterSignature = <
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
>(
  _func: SlackFunctionDefinition<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  >,
) => {
  const createContext: CreateActionHandlerContext<
    InputParameters,
    RequiredInput
  > = (
    args,
  ) => {
    const inputs = (args.inputs || {}) as FunctionRuntimeParameters<
      InputParameters,
      RequiredInput
    >;
    const DEFAULT_BODY = {
      type: "block_actions",
      actions: [DEFAULT_ACTION],
      function_data: {
        execution_id: "123",
        function: { callback_id: "456" },
        inputs,
      },
      interactivity: {
        interactor: {
          secret: "shhhh",
          id: "123",
        },
        interactivity_pointer: "123.asdf",
      },
      user: {
        id: "123",
        name: "asdf",
        team_id: "123",
      },
      team: {
        id: "123",
        domain: "asdf",
      },
      enterprise: null,
      is_enterprise_install: false,
      api_app_id: "123",
      token: "123",
      trigger_id: "123",
      response_url: "asdf",
    };
    const token = args.token || "slack-function-test-token";

    return {
      inputs,
      env: args.env || {},
      token,
      client: SlackAPI(token),
      team_id: args.team_id || "test-team-id",
      enterprise_id: "",
      action: args.action || DEFAULT_ACTION,
      body: args.body || DEFAULT_BODY,
    };
  };

  return { createContext };
};

// a basic function definition and associated block action router to test
const func = DefineFunction({
  callback_id: "id",
  title: "test",
  source_file: "whatever",
  input_parameters: {
    properties: {
      garbage: { type: Schema.types.string },
    },
    required: ["garbage"],
  },
});
const { createContext } = SlackActionHandlerTester(func);
const inputs = { garbage: "in, garbage out" };

const getRouter = () => {
  return BlockActionsRouter(func);
};

Deno.test("ActionsRouter", async (t) => {
  await t.step(
    "export method returns result of handler when matching action comes in and baseline handler context parameters are present and exist",
    async () => {
      const router = getRouter();
      let handlerCalled = false;
      router.addHandler(DEFAULT_ACTION.action_id, (ctx) => {
        assertExists(ctx.inputs);
        assertEquals<string>(ctx.inputs.garbage, inputs.garbage);
        assertExists(ctx.token);
        assertExists(ctx.action);
        assertExists(ctx.env);
        assertExists(ctx.client);
        handlerCalled = true;
      });
      await router(createContext({ inputs }));
      assertEquals(handlerCalled, true, "action handler not called!");
    },
  );
});

Deno.test("ActionsRouter action matching happy path", async (t) => {
  await t.step("simple string matching to action_id", async () => {
    const router = getRouter();
    let handlerCalled = false;
    router.addHandler(DEFAULT_ACTION.action_id, (ctx) => {
      assertExists(ctx.inputs);
      assertExists(ctx.token);
      assertExists(ctx.action);
      assertExists(ctx.env);
      assertExists(ctx.client);
      handlerCalled = true;
    });
    await router(createContext({ inputs }));
    assertEquals(handlerCalled, true, "action handler not called!");
  });
  await t.step("array of strings matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy();
    router.addHandler(["nope", DEFAULT_ACTION.action_id], handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("regex matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy();
    router.addHandler(/action/, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{action_id:string} matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy();
    router.addHandler({ action_id: DEFAULT_ACTION.action_id }, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{action_id:[string]} matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy();
    router.addHandler(
      { action_id: ["hahtrickedyou", DEFAULT_ACTION.action_id] },
      handler,
    );
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{action_id:regex} matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy();
    router.addHandler({ action_id: /action/ }, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{block_id:string} matching to block_id", async () => {
    const router = getRouter();
    const handler = mock.spy();
    router.addHandler({ block_id: DEFAULT_ACTION.block_id }, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{block_id:[string]} matching to block_id", async () => {
    const router = getRouter();
    const handler = mock.spy();
    router.addHandler(
      { block_id: ["lol", DEFAULT_ACTION.block_id] },
      handler,
    );
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{block_id:regex} matching to block_id", async () => {
    const router = getRouter();
    const handler = mock.spy();
    router.addHandler({ block_id: /block/ }, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step(
    "{block_id:string, action_id:string} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        block_id: DEFAULT_ACTION.block_id,
        action_id: DEFAULT_ACTION.action_id,
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:string, action_id:[string]} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        block_id: DEFAULT_ACTION.block_id,
        action_id: ["notthisoneeither", DEFAULT_ACTION.action_id],
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:string, action_id:regex} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler(
        { block_id: DEFAULT_ACTION.block_id, action_id: /action/ },
        handler,
      );
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:[string], action_id:string} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        block_id: ["notthistime", DEFAULT_ACTION.block_id],
        action_id: DEFAULT_ACTION.action_id,
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:[string], action_id:[string]} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        block_id: ["notthistime", DEFAULT_ACTION.block_id],
        action_id: ["gotyougood", DEFAULT_ACTION.action_id],
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:[string], action_id:regex} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        block_id: ["notthistime", DEFAULT_ACTION.block_id],
        action_id: /action/,
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:regex, action_id:string} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler(
        { block_id: /block/, action_id: DEFAULT_ACTION.action_id },
        handler,
      );
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:regex, action_id:[string]} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        block_id: /block/,
        action_id: ["hahanope", DEFAULT_ACTION.action_id],
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:regex, action_id:regex} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({ block_id: /block/, action_id: /action/ }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
});

Deno.test("ActionsRouter action matching sad path", async (t) => {
  await t.step("unhandled action should throw", async () => {
    const router = getRouter();
    await assertRejects(() => router(createContext({ inputs })));
  });

  await t.step("no false positives", async (t) => {
    await t.step(
      "not matching action_id: string",
      async () => {
        const router = getRouter();
        const handler = mock.spy();
        router.addHandler("nope", handler);

        await assertRejects(() => router(createContext({ inputs })));
        mock.assertSpyCalls(handler, 0);
      },
    );
  });

  await t.step(
    "not matching action_id: string[]",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler(["nope", "nuh uh"], handler);

      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );

  await t.step(
    "not matching action_id: regex",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler(/regex/, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );

  await t.step(
    "not matching {action_id: string}",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({ action_id: "nope" }, () => handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[]}",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({ action_id: ["nope", "nuh uh"] }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: regex}",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({ action_id: /regex/ }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {block_id: string}",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({ block_id: "nope" }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {block_id: string[]}",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({ block_id: ["nope", "nuh uh"] }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {block_id: regex}",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({ block_id: /regex/ }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: regex}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: "not good enough",
        block_id: /block/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: regex}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: "action_id",
        block_id: /noway/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: string[]}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: "not good enough",
        block_id: ["notthisonebut", "block_id"],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: string}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: "action_id",
        block_id: ["this", "wont", "work"],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: string}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: "not good enough",
        block_id: "block_id",
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: string}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: "action_id",
        block_id: "nicetry",
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: regex}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: ["not", "good", "enough"],
        block_id: /block/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: regex}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: ["decoy", "action_id"],
        block_id: /noway/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: string[]}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: ["not", "good", "enough"],
        block_id: ["notthisonebut", "block_id"],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: string}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: ["decoy", "action_id"],
        block_id: ["this", "wont", "work"],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: string}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: ["not", "good", "enough"],
        block_id: "block_id",
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: string}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: ["decoy", "action_id"],
        block_id: "nicetry",
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: regex, block_id: regex}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: /heh/,
        block_id: /block/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: regex, block_id: regex}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: /action/,
        block_id: /noway/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: regex, block_id: string[]}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: /hah/,
        block_id: ["notthisonebut", "block_id"],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: regex, block_id: string}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: /action/,
        block_id: ["this", "wont", "work"],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: regex, block_id: string}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: /huh/,
        block_id: "block_id",
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: regex, block_id: string}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy();
      router.addHandler({
        action_id: /action/,
        block_id: "nicetry",
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
});


================================================
FILE: src/functions/interactivity/block_actions_types.ts
================================================
/*
This is currently a pretty light type of a block_actions payload that we can fill out
once we have a way to share some of these payload types across our different libraries better
Some other references that may be useful to fill this out:
- https://api.slack.com/changelog/2020-09-full-state-on-view-submisson-and-block-actions
- https://api.slack.com/reference/interaction-payloads/block-actions#fields
- https://github.com/slackapi/bolt-js/blob/main/src/types/actions/block-action.ts
- https://api.slack.com/changelog/2018-05-file-threads-soon-tread
*/

import type { BlockAction, BlockElement } from "./block_kit_types.ts";

/**
 * @description Block Actions-specific type for the `body` property of a `block_actions` event
 */
export type BlockActionsBody = {
  actions: BlockAction[];
  /**
   * @description The encoded application ID the event was dispatched to, e.g. A123456.
   */
  api_app_id: string;
  /**
   * @description If applicable, the channel the Block Action interaction originated from.
   */
  channel?: {
    /**
     * @description Encoded channel ID, e.g. C123456.
     */
    id: string;
    /**
     * @description Channel name, without the "#" prefix.
     */
    name: string;
  };
  /**
   * @description If applicable, the enterprise associated with the workspace the Block Action interaction originated from. If not applicable, will be null.
   */
  enterprise: {
    /**
     * @description Encoded enterprise ID, e.g. E123456.
     */
    id: string;
    /**
     * @description Enterprise name.
     */
    name: string;
  } | null;
  /**
   * @description Whether this event originated from a workspace that is part of an enterprise installation.
   */
  is_enterprise_install: boolean;
  /**
   * @description Information about the source message this Block Action interaction originated from.
   * This may be optional in the case that the Block Action interaction originated from a view rather than a message.
   */
  message?: {
    /**
     * @description The encoded application ID the event was dispatched to, e.g. A123456.
     */
    app_id: string;
    /**
     * @description The {@link https://api.slack.com/block-kit Block Kit} elements included in the message.
     */
    blocks: BlockElement[];
    /**
     * @description Whether the {@link https://api.slack.com/messaging/managing#threading thread} has been locked.
     */
    is_locked: boolean;
    /**
     * @description If the {@link https://api.slack.com/messaging/managing#threading thread} has at least one reply, points to the most recent reply's `ts` value.
     */
    latest_reply?: string;
    /**
     * @description {@link https://api.slack.com/metadata Message metadata}, if any was attached to the message.
     */
    metadata?: {
      event_type: string;
      event_payload: {
        // deno-lint-ignore no-explicit-any
        [key: string]: any;
      };
    };
    /**
     * @description Total number of replies in the {@link https://api.slack.com/messaging/managing#threading thread}.
     */
    reply_count: number;
    /**
     * @description Array of up to 5 encoded user IDs (i.e. U12345) that replied in the {@link https://api.slack.com/messaging/managing#threading thread}.
     */
    reply_users: string[];
    /**
     * @description Total number of users that replied in the {@link https://api.slack.com/messaging/managing#threading thread}.
     */
    reply_users_count: number;
    /**
     * @description Encoded team ID, e.g. T123456.
     */
    team: string;
    /**
     * @description The text in the message. If the message was composed of Block Kit elements, this property would
     * contain the fallback text to display in constrained UIs (like notifications) or in screenreaders. See
     * {@link https://api.slack.com/methods/chat.postMessage#blocks_and_attachments the API documentation for use of text, blocks and attachments in messages}.
     */
    text: string;
    /**
     * @description Timestamp of the parent message. If the message is already a parent message, then this value will equal the `ts` value. Use this value if you want to post a message as a {@link https://api.slack.com/messaging/managing#threading threaded reply} to a particular message.
     */
    thread_ts: string;
    /**
     * @description Timestamp of the message.
     */
    ts: string;
    /**
     * @description The type of message. This is always "message."
     */
    type: "message";
    /**
     * @description Encoded user ID of the user that posted the message, e.g. U123456.
     */
    user: string;
  };
  /**
   * @description The workspace, or team, details the Block Kit interaction originated from.
   */
  team: {
    /**
     * @description The subdomain of the team, e.g. domain.slack.com
     */
    domain: string;
    /**
     * @description Encoded team ID, e.g. T123456.
     */
    id: string;
  };
  token: string;
  /**
   * @description A one-time use ID for opening modals or triggering other UI changes based on user interactions.
   */
  trigger_id: string;
  /**
   * @description Details for the user that initiated the Block Kit action.
   */
  user: {
    /**
     * @description Encoded user ID, e.g. U123456.
     */
    id: string;
    /**
     * @description User's handle as seen in the Slack client when e.g. at-notifying the user.
     */
    name: string;
    /**
     * @description The encoded team ID for the workspace, or team, where the Block Kit action originated from.
     */
    team_id: string;
  };
  // deno-lint-ignore no-explicit-any
  [key: string]: any;
  /* TODO: Other properties seen on this type that should be added at some point:
   * container: {}; // should be a reference to message or view, depending on the container for the block kit interactive componenet
   * message container looks like:
   *  "container": {
        "type": "message",
        "message_ts": "1663103912.870299",
        "channel_id": "C03DS3P5ED6",
        "is_ephemeral": false
      },
      view container looks like:
      container: { type: "view", view_id: "V041UDW806B" }
   * view: {}; // if the block kit interactive component was part of a view, details for the view are here
   * state: { values: {}}; // seen but usually empty?
   */
};


================================================
FILE: src/functions/interactivity/block_kit_types.ts
================================================
/**
 * @description A single Block Kit interactive component interaction.
 */
export type BlockAction =
  & {
    /**
     * @description Identifies the Block Kit interactive component that was interacted with.
     */
    action_id: string;
  }
  & BlockElement;

/**
 * @description A single Block element
 */
export type BlockElement = {
  /**
   * @description Identifies the block within a surface.
   */
  block_id: string;
  type: string;
  // deno-lint-ignore no-explicit-any
  [key: string]: any;
};


================================================
FILE: src/functions/interactivity/block_suggestion_router.ts
================================================
import type {
  ParameterSetDefinition,
  PossibleParameterKeys,
} from "../../parameters/types.ts";
import type { SlackFunctionDefinition } from "../definitions/mod.ts";
import { UnhandledEventError } from "../unhandled-event-error.ts";
import { enrichContext } from "../enrich-context.ts";
import type {
  FunctionDefinitionArgs,
  FunctionRuntimeParameters,
} from "../types.ts";
import type {
  BlockActionConstraint,
  BlockSuggestionHandler,
  RuntimeSuggestionContext,
  SuggestionContext,
} from "./types.ts";
import type { BlockAction } from "./block_kit_types.ts";
import {
  matchBasicConstraintField,
  normalizeConstraintToArray,
} from "./matchers.ts";

/**
 * Define a suggestion "router" and its input and output parameters for use in a Slack application. The SuggestionRouter will route incoming action events to action-specific handlers.
 * @param {SlackFunctionDefinition<InputParameters, OutputParameters, RequiredInput, RequiredOutput>} func Reference to your previously-created SlackFunction, defined via DefineFunction
 * @returns {SuggestionRouter}
 */
export const BlockSuggestionRouter = <
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
>(
  func: SlackFunctionDefinition<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  >,
) => {
  const router = new SuggestionRouter(func);

  // deno-lint-ignore no-explicit-any
  const exportedHandler: any = router.export();

  // deno-lint-ignore no-explicit-any
  exportedHandler.addHandler = ((...args: any) => {
    router.addHandler.apply(router, args);

    return exportedHandler;
  }) as typeof router.addHandler;

  return exportedHandler as
    & ReturnType<typeof router.export>
    & Pick<typeof router, "addHandler">;
};

export class SuggestionRouter<
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
> {
  private routes: Array<
    [BlockActionConstraint, BlockSuggestionHandler<typeof this.func.definition>]
  >;

  constructor(
    private func: SlackFunctionDefinition<
      InputParameters,
      OutputParameters,
      RequiredInput,
      RequiredOutput
    >,
  ) {
    this.func = func;
    this.routes = [];
  }

  /**
   * Add a suggestion handler for something that can match an action event.
   * @param {BlockActionConstraint} actionConstraint A BlockActionConstraintField(i.e. a string, array of strings or regular expression) or more complex BlockActionConstraintObject to match incoming block suggestion events. A BlockActionConstraintField parameter are matched with a block suggestion event's `action_id` property. A BlockActionConstraintObject parameter allows to match with other block suggestion event properties like `block_id` as well as `action_id`. If multiple properties are specified using BlockActionConstraintObject, then the event must match ALL provided BlockActionConstraintObject properties.
   * @returns {SuggestionRouter}
   */
  addHandler(
    actionConstraint: BlockActionConstraint,
    handler: BlockSuggestionHandler<
      FunctionDefinitionArgs<
        InputParameters,
        OutputParameters,
        RequiredInput,
        RequiredOutput
      >
    >,
  ): SuggestionRouter<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  > {
    this.routes.push([actionConstraint, handler]);
    return this;
  }

  /**
   * Returns a method handling routing of suggestion payloads to the appropriate suggestion handler.
   * The output of export() should be attached to the `blockSuggestion` export of your function.
   */
  export() {
    return async (
      context: RuntimeSuggestionContext<
        FunctionRuntimeParameters<InputParameters, RequiredInput>
      >,
    ) => {
      const suggestion = context.body;
      const handler = this.matchHandler(suggestion);
      if (handler === null) {
        throw new UnhandledEventError(
          `Received block suggestion payload with suggestion=${
            JSON.stringify(suggestion)
          } but this app has no suggestion handler defined to handle it!`,
        );
      }

      const enrichedContext = enrichContext(context) as SuggestionContext<
        FunctionRuntimeParameters<InputParameters, RequiredInput>
      >;

      return await handler(enrichedContext);
    };
  }

  /**
   * Return the first registered SuggestionHandler that matches the action and/or block ID string(s) provided.
   */
  matchHandler(
    action: BlockAction, // TODO: this type name is a bit misleading; BlockAction just has both action_id and block_id props on it, so it applies to both block_suggestion and block_action payloads
  ):
    | BlockSuggestionHandler<
      FunctionDefinitionArgs<
        InputParameters,
        OutputParameters,
        RequiredInput,
        RequiredOutput
      >
    >
    | null {
    for (let i = 0; i < this.routes.length; i++) {
      const route = this.routes[i];
      let [constraint, handler] = route;
      // Handle different constraint types below
      if (
        constraint instanceof RegExp || constraint instanceof Array ||
        typeof constraint === "string"
      ) {
        // Normalize simple string constraints to be an array of strings for consistency in handling inside this method.
        constraint = normalizeConstraintToArray(constraint);
        // Handle the case where the constraint is either a regex or an array of strings to match against action_id
        if (matchBasicConstraintField(constraint, "action_id", action)) {
          return handler;
        }
      } else {
        // Assumes an object as a constraint (type BlockActionConstraintObject)
        // Return first match *within* any of the defined fields on the constaint object, but ensure there is a match on *all* defined fields
        // Effectively a logical AND across the action_id and block_id field(s)
        // If either of the constraint fields are not defined, pre-set them to have matched so we can effectively
        // ignore them when determining if we have a match by &&'ing them
        let actionIDMatched = constraint.action_id ? false : true;
        let blockIDMatched = constraint.block_id ? false : true;
        if (constraint.action_id) {
          actionIDMatched = matchBasicConstraintField(
            normalizeConstraintToArray(constraint.action_id),
            "action_id",
            action,
          );
        }
        if (constraint.block_id) {
          blockIDMatched = matchBasicConstraintField(
            normalizeConstraintToArray(constraint.block_id),
            "block_id",
            action,
          );
        }
        if (blockIDMatched && actionIDMatched) {
          return handler;
        }
      }
    }
    return null;
  }
}


================================================
FILE: src/functions/interactivity/block_suggestion_router_test.ts
================================================
import { SlackAPI } from "../../deps.ts";
import {
  assertEquals,
  assertExists,
  assertRejects,
  mock,
} from "../../dev_deps.ts";
import { BlockSuggestionRouter } from "./block_suggestion_router.ts";
import type { SuggestionContext } from "./types.ts";
import type {
  FunctionParameters,
  FunctionRuntimeParameters,
} from "../types.ts";
import type {
  ParameterSetDefinition,
  PossibleParameterKeys,
} from "../../parameters/types.ts";
import type { SlackFunctionDefinition } from "../definitions/mod.ts";
import { DefineFunction, Schema } from "../../mod.ts";

// Helper test types
// TODO: maybe we want to export this for userland usage at some point?
// Very much a direct copy from the existing main function tester types and utilties in src/functions/tester
type SlackSuggestionHandlerTesterArgs<
  InputParameters extends FunctionParameters,
> =
  & Partial<
    SuggestionContext<InputParameters>
  >
  & {
    inputs: InputParameters;
  };

type CreateSuggestionHandlerContext<
  InputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
> = {
  (
    args: SlackSuggestionHandlerTesterArgs<
      FunctionRuntimeParameters<InputParameters, RequiredInput>
    >,
  ): SuggestionContext<
    FunctionRuntimeParameters<InputParameters, RequiredInput>
  >;
};

type SlackSuggestionHandlerTesterResponse<
  InputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
> = {
  createContext: CreateSuggestionHandlerContext<InputParameters, RequiredInput>;
};

type SlackSuggestionHandlerTesterSignature = {
  <
    InputParameters extends ParameterSetDefinition,
    OutputParameters extends ParameterSetDefinition,
    RequiredInput extends PossibleParameterKeys<InputParameters>,
    RequiredOutput extends PossibleParameterKeys<OutputParameters>,
  >(
    func: SlackFunctionDefinition<
      InputParameters,
      OutputParameters,
      RequiredInput,
      RequiredOutput
    >,
  ): SlackSuggestionHandlerTesterResponse<
    InputParameters,
    RequiredInput
  >;
};

const DEFAULT_ACTION_ID = "action_id";
const DEFAULT_BLOCK_ID = "block_id";
// deno-lint-ignore no-explicit-any
const generateSuggestion = (inputs: any) => ({
  block_id: DEFAULT_BLOCK_ID,
  action_id: DEFAULT_ACTION_ID,
  value: "ohai",
  type: "block_suggestion",
  function_data: {
    execution_id: "123",
    function: { callback_id: "456" },
    inputs,
  },
  interactivity: {
    interactor: {
      secret: "shhhh",
      id: "123",
    },
    interactivity_pointer: "123.asdf",
  },
  user: {
    id: "123",
    name: "asdf",
    team_id: "123",
  },
  team: {
    id: "123",
    domain: "asdf",
  },
  enterprise: null,
  api_app_id: "123",
});

const SlackSuggestionHandlerTester: SlackSuggestionHandlerTesterSignature = <
  InputParameters extends ParameterSetDefinition,
  OutputParameters extends ParameterSetDefinition,
  RequiredInput extends PossibleParameterKeys<InputParameters>,
  RequiredOutput extends PossibleParameterKeys<OutputParameters>,
>(
  _func: SlackFunctionDefinition<
    InputParameters,
    OutputParameters,
    RequiredInput,
    RequiredOutput
  >,
) => {
  const createContext: CreateSuggestionHandlerContext<
    InputParameters,
    RequiredInput
  > = (
    args,
  ) => {
    const inputs = (args.inputs || {}) as FunctionRuntimeParameters<
      InputParameters,
      RequiredInput
    >;
    const token = args.token || "slack-function-test-token";

    return {
      inputs,
      env: args.env || {},
      token,
      client: SlackAPI(token),
      team_id: args.team_id || "test-team-id",
      enterprise_id: "",
      body: args.body || generateSuggestion(inputs),
    };
  };

  return { createContext };
};

// a basic function definition and associated block action router to test
const func = DefineFunction({
  callback_id: "id",
  title: "test",
  source_file: "whatever",
  input_parameters: {
    properties: {
      garbage: { type: Schema.types.string },
    },
    required: ["garbage"],
  },
});
const { createContext } = SlackSuggestionHandlerTester(func);
const inputs = { garbage: "in, garbage out" };

const getRouter = () => {
  return BlockSuggestionRouter(func);
};

Deno.test("SuggestionRouter", async (t) => {
  await t.step(
    "export method returns result of handler when matching suggestion comes in and baseline handler context parameters are present and exist",
    async () => {
      const router = getRouter();
      let handlerCalled = false;
      router.addHandler(DEFAULT_ACTION_ID, (ctx) => {
        assertExists(ctx.inputs);
        assertEquals<string>(ctx.inputs.garbage, inputs.garbage);
        assertExists(ctx.token);
        assertExists(ctx.body);
        assertExists(ctx.env);
        assertExists(ctx.client);
        handlerCalled = true;
        return { options: [] };
      });
      await router(createContext({ inputs }));
      assertEquals(handlerCalled, true, "suggestion handler not called!");
    },
  );
});

Deno.test("SuggestionRouter matching happy path", async (t) => {
  await t.step("simple string matching to action_id", async () => {
    const router = getRouter();
    let handlerCalled = false;
    router.addHandler(DEFAULT_ACTION_ID, (ctx) => {
      assertExists(ctx.inputs);
      assertExists(ctx.token);
      assertExists(ctx.body);
      assertExists(ctx.env);
      assertExists(ctx.client);
      handlerCalled = true;
      return { options: [] };
    });
    await router(createContext({ inputs }));
    assertEquals(handlerCalled, true, "suggestion handler not called!");
  });
  await t.step("array of strings matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy(() => ({ options: [] }));
    router.addHandler(["nope", DEFAULT_ACTION_ID], handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("regex matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy(() => ({ options: [] }));
    router.addHandler(/action/, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{action_id:string} matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy(() => ({ options: [] }));
    router.addHandler({ action_id: DEFAULT_ACTION_ID }, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{action_id:[string]} matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy(() => ({ options: [] }));
    router.addHandler(
      { action_id: ["hahtrickedyou", DEFAULT_ACTION_ID] },
      handler,
    );
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{action_id:regex} matching to action_id", async () => {
    const router = getRouter();
    const handler = mock.spy(() => ({ options: [] }));
    router.addHandler({ action_id: /action/ }, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{block_id:string} matching to block_id", async () => {
    const router = getRouter();
    const handler = mock.spy(() => ({ options: [] }));
    router.addHandler({ block_id: DEFAULT_BLOCK_ID }, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{block_id:[string]} matching to block_id", async () => {
    const router = getRouter();
    const handler = mock.spy(() => ({ options: [] }));
    router.addHandler(
      { block_id: ["lol", DEFAULT_BLOCK_ID] },
      handler,
    );
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step("{block_id:regex} matching to block_id", async () => {
    const router = getRouter();
    const handler = mock.spy(() => ({ options: [] }));
    router.addHandler({ block_id: /block/ }, handler);
    await router(createContext({ inputs }));
    mock.assertSpyCalls(handler, 1);
  });
  await t.step(
    "{block_id:string, action_id:string} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        block_id: DEFAULT_BLOCK_ID,
        action_id: DEFAULT_ACTION_ID,
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:string, action_id:[string]} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        block_id: DEFAULT_BLOCK_ID,
        action_id: ["notthisoneeither", DEFAULT_ACTION_ID],
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:string, action_id:regex} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler(
        { block_id: DEFAULT_BLOCK_ID, action_id: /action/ },
        handler,
      );
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:[string], action_id:string} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        block_id: ["notthistime", DEFAULT_BLOCK_ID],
        action_id: DEFAULT_ACTION_ID,
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:[string], action_id:[string]} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        block_id: ["notthistime", DEFAULT_BLOCK_ID],
        action_id: ["gotyougood", DEFAULT_ACTION_ID],
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:[string], action_id:regex} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        block_id: ["notthistime", DEFAULT_BLOCK_ID],
        action_id: /action/,
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:regex, action_id:string} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler(
        { block_id: /block/, action_id: DEFAULT_ACTION_ID },
        handler,
      );
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:regex, action_id:[string]} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        block_id: /block/,
        action_id: ["hahanope", DEFAULT_ACTION_ID],
      }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
  await t.step(
    "{block_id:regex, action_id:regex} matching to both block_id and action_id",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({ block_id: /block/, action_id: /action/ }, handler);
      await router(createContext({ inputs }));
      mock.assertSpyCalls(handler, 1);
    },
  );
});

Deno.test("SuggestionRouter matching sad path", async (t) => {
  await t.step("unhandled suggestion should throw", async () => {
    const router = getRouter();
    await assertRejects(() => router(createContext({ inputs })));
  });

  await t.step("no false positives", async (t) => {
    await t.step(
      "not matching action_id: string",
      async () => {
        const router = getRouter();
        const handler = mock.spy(() => ({ options: [] }));
        router.addHandler("nope", handler);

        await assertRejects(() => router(createContext({ inputs })));
        mock.assertSpyCalls(handler, 0);
      },
    );
  });

  await t.step(
    "not matching action_id: string[]",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler(["nope", "nuh uh"], handler);

      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );

  await t.step(
    "not matching action_id: regex",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler(/regex/, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );

  await t.step(
    "not matching {action_id: string}",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({ action_id: "nope" }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[]}",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({ action_id: ["nope", "nuh uh"] }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: regex}",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({ action_id: /regex/ }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {block_id: string}",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({ block_id: "nope" }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {block_id: string[]}",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({ block_id: ["nope", "nuh uh"] }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {block_id: regex}",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({ block_id: /regex/ }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: regex}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: "not good enough",
        block_id: /block/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: regex}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: DEFAULT_ACTION_ID,
        block_id: /noway/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: string[]}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: "not good enough",
        block_id: ["notthisonebut", DEFAULT_BLOCK_ID],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: string}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: DEFAULT_ACTION_ID,
        block_id: ["this", "wont", "work"],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: string}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: "not good enough",
        block_id: DEFAULT_BLOCK_ID,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string, block_id: string}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: DEFAULT_ACTION_ID,
        block_id: "nicetry",
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: regex}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: ["not", "good", "enough"],
        block_id: /block/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: regex}, block_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: ["decoy", DEFAULT_ACTION_ID],
        block_id: /noway/,
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
      mock.assertSpyCalls(handler, 0);
    },
  );
  await t.step(
    "not matching {action_id: string[], block_id: string[]}, action_id does not match",
    async () => {
      const router = getRouter();
      const handler = mock.spy(() => ({ options: [] }));
      router.addHandler({
        action_id: ["not", "good", "enough"],
        block_id: ["notthisonebut", DEFAULT_BLOCK_ID],
      }, handler);
      await assertRejects(() => router(createContext({ inputs })));
 
Download .txt
gitextract_v0ijlb09/

├── .github/
│   ├── CODEOWNERS
│   ├── CONTRIBUTING.md
│   ├── ISSUE_TEMPLATE/
│   │   ├── bug.md
│   │   ├── feature.md
│   │   └── question.md
│   ├── PULL_REQUEST_TEMPLATE.md
│   ├── dependabot.yml
│   ├── maintainers_guide.md
│   └── workflows/
│       ├── deno.yml
│       ├── dependencies.yml
│       ├── e2e.yml
│       ├── npm-publish.yml
│       ├── npm.yml
│       ├── publish.yml
│       └── samples.yml
├── .gitignore
├── .vscode/
│   └── settings.json
├── LICENSE
├── README.md
├── deno.jsonc
├── docs/
│   ├── datastores.md
│   ├── events.md
│   ├── functions-action-handlers.md
│   ├── functions-suggestion-handlers.md
│   ├── functions-view-handlers.md
│   ├── functions.md
│   ├── manifest.md
│   ├── types.md
│   └── workflows.md
├── scripts/
│   ├── build_npm.ts
│   ├── bundle.ts
│   └── imports/
│       └── update.ts
├── src/
│   ├── README.md
│   ├── datastore/
│   │   ├── datastore_test.ts
│   │   ├── mod.ts
│   │   └── types.ts
│   ├── deps.ts
│   ├── dev_deps.ts
│   ├── events/
│   │   ├── events_test.ts
│   │   ├── mod.ts
│   │   └── types.ts
│   ├── functions/
│   │   ├── definitions/
│   │   │   ├── connector-function.ts
│   │   │   ├── connector-function_test.ts
│   │   │   ├── mod.ts
│   │   │   ├── slack-function.ts
│   │   │   └── slack-function_test.ts
│   │   ├── enrich-context.ts
│   │   ├── enrich-context_test.ts
│   │   ├── interactivity/
│   │   │   ├── block_actions_router.ts
│   │   │   ├── block_actions_router_test.ts
│   │   │   ├── block_actions_types.ts
│   │   │   ├── block_kit_types.ts
│   │   │   ├── block_suggestion_router.ts
│   │   │   ├── block_suggestion_router_test.ts
│   │   │   ├── block_suggestion_types.ts
│   │   │   ├── matchers.ts
│   │   │   ├── mod.ts
│   │   │   ├── types.ts
│   │   │   ├── view_router.ts
│   │   │   ├── view_router_test.ts
│   │   │   └── view_types.ts
│   │   ├── mod.ts
│   │   ├── slack-function.ts
│   │   ├── slack-function_test.ts
│   │   ├── tester/
│   │   │   ├── function_tester_test.ts
│   │   │   ├── mod.ts
│   │   │   └── types.ts
│   │   ├── types.ts
│   │   ├── types_base_runtime_function_handler_test.ts
│   │   ├── types_runtime_slack_function_handler_test.ts
│   │   └── unhandled-event-error.ts
│   ├── manifest/
│   │   ├── errors.ts
│   │   ├── errors_test.ts
│   │   ├── manifest_schema.ts
│   │   ├── manifest_test.ts
│   │   ├── mod.ts
│   │   ├── types.ts
│   │   └── types_util.ts
│   ├── mod.ts
│   ├── mod_test.ts
│   ├── parameters/
│   │   ├── define_property.ts
│   │   ├── define_property_test.ts
│   │   ├── definition_types.ts
│   │   ├── mod.ts
│   │   ├── param.ts
│   │   ├── param_test.ts
│   │   ├── parameter-variable_test.ts
│   │   ├── types.ts
│   │   ├── with-untyped-object-proxy.ts
│   │   └── with-untyped-object-proxy_test.ts
│   ├── providers/
│   │   └── oauth2/
│   │       ├── mod.ts
│   │       ├── oauth2_test.ts
│   │       └── types.ts
│   ├── schema/
│   │   ├── mod.ts
│   │   ├── providers/
│   │   │   ├── mod.ts
│   │   │   └── oauth2/
│   │   │       ├── mod.ts
│   │   │       └── types.ts
│   │   ├── schema_types.ts
│   │   ├── slack/
│   │   │   ├── functions/
│   │   │   │   ├── _scripts/
│   │   │   │   │   ├── .gitignore
│   │   │   │   │   ├── README.md
│   │   │   │   │   ├── generate
│   │   │   │   │   └── src/
│   │   │   │   │       ├── templates/
│   │   │   │   │       │   ├── mod.ts
│   │   │   │   │       │   ├── template_function.ts
│   │   │   │   │       │   ├── template_mod.ts
│   │   │   │   │       │   ├── test_template.ts
│   │   │   │   │       │   ├── types.ts
│   │   │   │   │       │   ├── utils.ts
│   │   │   │   │       │   └── utils_test.ts
│   │   │   │   │       ├── test/
│   │   │   │   │       │   └── data/
│   │   │   │   │       │       └── function.json
│   │   │   │   │       ├── types.ts
│   │   │   │   │       ├── utils.ts
│   │   │   │   │       ├── utils_test.ts
│   │   │   │   │       └── write_function_files.ts
│   │   │   │   ├── add_bookmark.ts
│   │   │   │   ├── add_bookmark_test.ts
│   │   │   │   ├── add_pin.ts
│   │   │   │   ├── add_pin_test.ts
│   │   │   │   ├── add_reaction.ts
│   │   │   │   ├── add_reaction_test.ts
│   │   │   │   ├── add_user_to_usergroup.ts
│   │   │   │   ├── add_user_to_usergroup_test.ts
│   │   │   │   ├── archive_channel.ts
│   │   │   │   ├── archive_channel_test.ts
│   │   │   │   ├── canvas_copy.ts
│   │   │   │   ├── canvas_copy_test.ts
│   │   │   │   ├── canvas_create.ts
│   │   │   │   ├── canvas_create_test.ts
│   │   │   │   ├── canvas_update_content.ts
│   │   │   │   ├── canvas_update_content_test.ts
│   │   │   │   ├── channel_canvas_create.ts
│   │   │   │   ├── channel_canvas_create_test.ts
│   │   │   │   ├── create_channel.ts
│   │   │   │   ├── create_channel_test.ts
│   │   │   │   ├── create_usergroup.ts
│   │   │   │   ├── create_usergroup_test.ts
│   │   │   │   ├── delay.ts
│   │   │   │   ├── delay_test.ts
│   │   │   │   ├── invite_user_to_channel.ts
│   │   │   │   ├── invite_user_to_channel_test.ts
│   │   │   │   ├── mod.ts
│   │   │   │   ├── open_form.ts
│   │   │   │   ├── open_form_test.ts
│   │   │   │   ├── remove_reaction.ts
│   │   │   │   ├── remove_reaction_test.ts
│   │   │   │   ├── remove_user_from_usergroup.ts
│   │   │   │   ├── remove_user_from_usergroup_test.ts
│   │   │   │   ├── reply_in_thread.ts
│   │   │   │   ├── reply_in_thread_test.ts
│   │   │   │   ├── send_dm.ts
│   │   │   │   ├── send_dm_test.ts
│   │   │   │   ├── send_ephemeral_message.ts
│   │   │   │   ├── send_ephemeral_message_test.ts
│   │   │   │   ├── send_message.ts
│   │   │   │   ├── send_message_test.ts
│   │   │   │   ├── share_canvas.ts
│   │   │   │   ├── share_canvas_in_thread.ts
│   │   │   │   ├── share_canvas_in_thread_test.ts
│   │   │   │   ├── share_canvas_test.ts
│   │   │   │   ├── update_channel_topic.ts
│   │   │   │   └── update_channel_topic_test.ts
│   │   │   ├── mod.ts
│   │   │   ├── schema_types.ts
│   │   │   └── types/
│   │   │       ├── custom/
│   │   │       │   ├── custom_slack_types_test.ts
│   │   │       │   ├── form_input.ts
│   │   │       │   ├── interactivity.ts
│   │   │       │   ├── message_context.ts
│   │   │       │   ├── mod.ts
│   │   │       │   └── user_context.ts
│   │   │       └── mod.ts
│   │   └── types.ts
│   ├── test_utils.ts
│   ├── test_utils_test.ts
│   ├── type_utils.ts
│   ├── types/
│   │   ├── mod.ts
│   │   ├── types.ts
│   │   └── types_test.ts
│   ├── types.ts
│   └── workflows/
│       ├── mod.ts
│       ├── types.ts
│       └── workflow-step.ts
└── tests/
    └── integration/
        ├── functions/
        │   └── runtime_context/
        │       ├── array_parameters_test.ts
        │       ├── custom_type_parameters_test.ts
        │       ├── empty_undefined_parameters_test.ts
        │       ├── incomplete_error_status_test.ts
        │       ├── input_parameter_optionality_test.ts
        │       ├── input_parameters_test.ts
        │       ├── output_parameter_optionality_test.ts
        │       ├── output_parameters_test.ts
        │       ├── typed_object_property_test.ts
        │       └── untyped_object_property_test.ts
        ├── parameters/
        │   ├── parameter_variable_test.ts
        │   └── parameter_variable_unwrapped_test.ts
        ├── schema/
        │   └── slack/
        │       └── functions/
        │           └── _scripts/
        │               └── write_function_files_test.ts
        └── workflows/
            └── workflows_test.ts
Download .txt
SYMBOL INDEX (363 symbols across 56 files)

FILE: scripts/bundle.ts
  function bundle (line 5) | async function bundle(options: {

FILE: src/datastore/mod.ts
  class SlackDatastore (line 31) | class SlackDatastore<
    method constructor (line 39) | constructor(
    method registerAttributeTypes (line 50) | registerAttributeTypes(manifest: SlackManifest) {
    method export (line 58) | export(): ManifestDatastoreSchema {

FILE: src/datastore/types.ts
  type InvalidDatastoreTypes (line 9) | type InvalidDatastoreTypes =
  type ValidDatastoreTypes (line 13) | type ValidDatastoreTypes = Exclude<
  type SlackDatastoreAttribute (line 19) | type SlackDatastoreAttribute = {
  type SlackDatastoreAttributes (line 24) | type SlackDatastoreAttributes = Record<string, SlackDatastoreAttribute>;
  type SlackDatastoreDefinition (line 26) | type SlackDatastoreDefinition<
  type ISlackDatastore (line 38) | interface ISlackDatastore {
  type SlackDatastoreItem (line 44) | type SlackDatastoreItem<Attributes extends SlackDatastoreAttributes> = {
  type PartialSlackDatastoreItem (line 51) | type PartialSlackDatastoreItem<
  type OptionalPartial (line 56) | type OptionalPartial<T extends any> = {

FILE: src/events/mod.ts
  class CustomEvent (line 19) | class CustomEvent<Def extends CustomEventDefinition>
    method constructor (line 25) | constructor(
    method generateReferenceString (line 34) | private generateReferenceString() {
    method toString (line 38) | toString() {
    method toJSON (line 42) | toJSON() {
    method registerParameterTypes (line 46) | registerParameterTypes(manifest: SlackManifest) {
    method export (line 57) | export(): ManifestCustomEventSchema {

FILE: src/events/types.ts
  type AcceptedEventTypes (line 9) | type AcceptedEventTypes =
  type CustomEventDefinition (line 13) | type CustomEventDefinition =
  type DefineEventSignature (line 23) | type DefineEventSignature = {
  type ICustomEvent (line 27) | interface ICustomEvent {

FILE: src/functions/definitions/connector-function.ts
  class ConnectorFunctionDefinition (line 32) | class ConnectorFunctionDefinition<
    method constructor (line 47) | constructor(

FILE: src/functions/definitions/connector-function_test.ts
  type emptyParameterType (line 13) | type emptyParameterType = Record<string, never>;

FILE: src/functions/definitions/slack-function.ts
  class SlackFunctionDefinition (line 36) | class SlackFunctionDefinition<
    method constructor (line 51) | constructor(
    method registerParameterTypes (line 63) | registerParameterTypes(manifest: SlackManifest) {
    method export (line 70) | export(): ManifestFunctionSchema {
  function isCustomFunctionDefinition (line 83) | function isCustomFunctionDefinition<

FILE: src/functions/definitions/slack-function_test.ts
  type emptyParameterType (line 18) | type emptyParameterType = Record<string, never>;

FILE: src/functions/interactivity/block_actions_router.ts
  class ActionsRouter (line 59) | class ActionsRouter<
    method constructor (line 69) | constructor(
    method addHandler (line 86) | addHandler(
    method export (line 110) | export() {
    method matchHandler (line 137) | matchHandler(

FILE: src/functions/interactivity/block_actions_router_test.ts
  type SlackActionHandlerTesterArgs (line 25) | type SlackActionHandlerTesterArgs<InputParameters extends FunctionParame...
  type CreateActionHandlerContext (line 33) | type CreateActionHandlerContext<
  type SlackActionHandlerTesterResponse (line 46) | type SlackActionHandlerTesterResponse<
  type SlackActionHandlerTesterSignature (line 53) | type SlackActionHandlerTesterSignature = {
  constant DEFAULT_ACTION (line 73) | const DEFAULT_ACTION: BlockAction = {

FILE: src/functions/interactivity/block_actions_types.ts
  type BlockActionsBody (line 16) | type BlockActionsBody = {

FILE: src/functions/interactivity/block_kit_types.ts
  type BlockAction (line 4) | type BlockAction =
  type BlockElement (line 16) | type BlockElement = {

FILE: src/functions/interactivity/block_suggestion_router.ts
  class SuggestionRouter (line 59) | class SuggestionRouter<
    method constructor (line 69) | constructor(
    method addHandler (line 86) | addHandler(
    method export (line 110) | export() {
    method matchHandler (line 137) | matchHandler(

FILE: src/functions/interactivity/block_suggestion_router_test.ts
  type SlackSuggestionHandlerTesterArgs (line 24) | type SlackSuggestionHandlerTesterArgs<
  type CreateSuggestionHandlerContext (line 34) | type CreateSuggestionHandlerContext<
  type SlackSuggestionHandlerTesterResponse (line 47) | type SlackSuggestionHandlerTesterResponse<
  type SlackSuggestionHandlerTesterSignature (line 54) | type SlackSuggestionHandlerTesterSignature = {
  constant DEFAULT_ACTION_ID (line 73) | const DEFAULT_ACTION_ID = "action_id";
  constant DEFAULT_BLOCK_ID (line 74) | const DEFAULT_BLOCK_ID = "block_id";

FILE: src/functions/interactivity/block_suggestion_types.ts
  type BlockSuggestionBody (line 7) | type BlockSuggestionBody =

FILE: src/functions/interactivity/matchers.ts
  function normalizeConstraintToArray (line 8) | function normalizeConstraintToArray(constraint: BasicConstraintField) {
  function matchBasicConstraintField (line 15) | function matchBasicConstraintField(

FILE: src/functions/interactivity/types.ts
  type BlockActionHandler (line 18) | type BlockActionHandler<Definition> = Definition extends
  type BlockSuggestionHandler (line 27) | type BlockSuggestionHandler<Definition> = Definition extends
  type BlockSuggestionHandlerResponse (line 35) | type BlockSuggestionHandlerResponse =
  type BlockSuggestionHandlerOptionsResponse (line 39) | type BlockSuggestionHandlerOptionsResponse = {
  type BlockSuggestionHandlerOptionGroupsResponse (line 43) | type BlockSuggestionHandlerOptionGroupsResponse = {
  type MenuOptionGroup (line 47) | type MenuOptionGroup = {
  type MenuOption (line 58) | type MenuOption = {
  type PlainTextObject (line 69) | type PlainTextObject = {
  type ViewSubmissionHandler (line 81) | type ViewSubmissionHandler<Definition> = Definition extends
  type ViewClosedHandler (line 90) | type ViewClosedHandler<Definition> = Definition extends
  type UnhandledEventHandler (line 99) | type UnhandledEventHandler<Definition> = Definition extends
  type BaseInteractivityContext (line 108) | type BaseInteractivityContext<
  type ActionContext (line 114) | type ActionContext<InputParameters extends FunctionParameters> =
  type SuggestionContext (line 118) | type SuggestionContext<InputParameters extends FunctionParameters> =
  type ViewSubmissionContext (line 122) | type ViewSubmissionContext<InputParameters extends FunctionParameters> =
  type ViewClosedContext (line 126) | type ViewClosedContext<InputParameters extends FunctionParameters> =
  type UnhandledEventContext (line 130) | type UnhandledEventContext<InputParameters extends FunctionParameters> =
  type ActionSpecificContext (line 136) | type ActionSpecificContext<InputParameters extends FunctionParameters> = {
  type SuggestionSpecificContext (line 141) | type SuggestionSpecificContext<InputParameters extends FunctionParameter...
  type ViewSubmissionSpecificContext (line 145) | type ViewSubmissionSpecificContext<InputParameters extends FunctionParam...
  type ViewClosedSpecificContext (line 151) | type ViewClosedSpecificContext<InputParameters extends FunctionParameter...
  type UnhandledEventSpecificContext (line 156) | type UnhandledEventSpecificContext<InputParameters extends FunctionParam...
  type BlockActionInvocationBody (line 167) | type BlockActionInvocationBody<
  type BlockSuggestionInvocationBody (line 173) | type BlockSuggestionInvocationBody<
  type ViewSubmissionInvocationBody (line 179) | type ViewSubmissionInvocationBody<
  type ViewClosedInvocationBody (line 185) | type ViewClosedInvocationBody<
  type FunctionInteractivity (line 191) | type FunctionInteractivity<InputParameters extends FunctionParameters> = {
  type FunctionData (line 196) | type FunctionData<InputParameters extends FunctionParameters> = {
  type Interactivity (line 204) | type Interactivity = {
  type UserContext (line 209) | type UserContext = {
  type BlockActionConstraint (line 220) | type BlockActionConstraint =
  type BlockActionConstraintObject (line 228) | type BlockActionConstraintObject = {
  type ViewConstraintObject (line 239) | type ViewConstraintObject = {
  type BasicConstraintField (line 244) | type BasicConstraintField = string | string[] | RegExp;
  type RuntimeSuggestionContext (line 247) | type RuntimeSuggestionContext<
  type RuntimeActionContext (line 253) | type RuntimeActionContext<InputParameters extends FunctionParameters> =
  type RuntimeViewSubmissionContext (line 257) | type RuntimeViewSubmissionContext<
  type RuntimeViewClosedContext (line 263) | type RuntimeViewClosedContext<

FILE: src/functions/interactivity/view_router.ts
  class ViewRouter (line 42) | class ViewRouter<
    method constructor (line 61) | constructor(
    method addClosedHandler (line 84) | addClosedHandler(
    method addSubmissionHandler (line 114) | addSubmissionHandler(
    method viewClosed (line 141) | async viewClosed(
    method viewSubmission (line 162) | async viewSubmission(
    method matchHandler (line 180) | private matchHandler(

FILE: src/functions/interactivity/view_router_test.ts
  type SlackViewSubmissionHandlerTesterArgs (line 31) | type SlackViewSubmissionHandlerTesterArgs<
  type SlackViewClosedHandlerTesterArgs (line 41) | type SlackViewClosedHandlerTesterArgs<
  type CreateViewSubmissionHandlerContext (line 51) | type CreateViewSubmissionHandlerContext<
  type CreateViewClosedHandlerContext (line 64) | type CreateViewClosedHandlerContext<
  type SlackViewSubmissionHandlerTesterResponse (line 77) | type SlackViewSubmissionHandlerTesterResponse<
  type SlackViewClosedHandlerTesterResponse (line 87) | type SlackViewClosedHandlerTesterResponse<
  type SlackViewSubmissionHandlerTesterSignature (line 97) | type SlackViewSubmissionHandlerTesterSignature = {
  type SlackViewClosedHandlerTesterSignature (line 116) | type SlackViewClosedHandlerTesterSignature = {
  constant DEFAULT_VIEW (line 135) | const DEFAULT_VIEW: View = {

FILE: src/functions/interactivity/view_types.ts
  type ViewEvents (line 10) | type ViewEvents = "view_submission" | "view_closed";
  type BaseViewBody (line 15) | type BaseViewBody = {
  type ViewSubmissionBody (line 79) | type ViewSubmissionBody =
  type ViewClosedBody (line 92) | type ViewClosedBody =
  type View (line 105) | type View = {

FILE: src/functions/tester/mod.ts
  constant DEFAULT_FUNCTION_TESTER_TITLE (line 12) | const DEFAULT_FUNCTION_TESTER_TITLE = "Function Test Title";

FILE: src/functions/tester/types.ts
  type SlackFunctionTesterArgs (line 12) | type SlackFunctionTesterArgs<
  type CreateFunctionContext (line 22) | type CreateFunctionContext<
  type SlackFunctionTesterResponse (line 35) | type SlackFunctionTesterResponse<
  type SlackFunctionTesterSignature (line 43) | type SlackFunctionTesterSignature = {

FILE: src/functions/types.ts
  type FunctionInvocationBody (line 41) | type FunctionInvocationBody = {
  type FunctionInputRuntimeType (line 63) | type FunctionInputRuntimeType<
  type UnknownRuntimeType (line 106) | type UnknownRuntimeType = any;
  type TypedObjectFunctionInputRuntimeType (line 108) | type TypedObjectFunctionInputRuntimeType<
  type TypedArrayFunctionInputRuntimeType (line 128) | type TypedArrayFunctionInputRuntimeType<
  type FunctionRuntimeParameters (line 135) | type FunctionRuntimeParameters<
  type AsyncFunctionHandler (line 150) | type AsyncFunctionHandler<
  type SyncFunctionHandler (line 160) | type SyncFunctionHandler<
  type RuntimeSlackFunctionHandler (line 173) | type RuntimeSlackFunctionHandler<Definition> = Definition extends
  type BaseRuntimeSlackFunctionHandler (line 184) | type BaseRuntimeSlackFunctionHandler<
  type EnrichedSlackFunctionHandler (line 202) | type EnrichedSlackFunctionHandler<Definition> = Definition extends
  type BaseEnrichedSlackFunctionHandler (line 213) | type BaseEnrichedSlackFunctionHandler<
  type SuccessfulFunctionReturnArgs (line 232) | type SuccessfulFunctionReturnArgs<
  type ErroredFunctionReturnArgs (line 242) | type ErroredFunctionReturnArgs<OutputParameters extends FunctionParamete...
  type PendingFunctionReturnArgs (line 246) | type PendingFunctionReturnArgs = {
  type FunctionHandlerReturnArgs (line 252) | type FunctionHandlerReturnArgs<
  type BaseRuntimeFunctionContext (line 261) | type BaseRuntimeFunctionContext<
  type FunctionContextEnrichment (line 287) | type FunctionContextEnrichment = {
  type RuntimeFunctionContext (line 292) | type RuntimeFunctionContext<InputParameters extends FunctionParameters> =
  type FunctionContext (line 299) | type FunctionContext<
  type FunctionParameters (line 304) | type FunctionParameters = {
  type ISlackFunctionDefinition (line 309) | interface ISlackFunctionDefinition<
  type SlackFunctionDefinitionArgs (line 327) | type SlackFunctionDefinitionArgs<
  type FunctionDefinitionArgs (line 341) | type FunctionDefinitionArgs<
  type SlackFunctionType (line 364) | type SlackFunctionType<Definition> = Definition extends
  type RuntimeUnhandledEventContext (line 412) | type RuntimeUnhandledEventContext<

FILE: src/functions/types_base_runtime_function_handler_test.ts
  type Inputs (line 9) | type Inputs = {
  type Outputs (line 12) | type Outputs = {
  type Inputs (line 31) | type Inputs = Record<never, never>;
  type Outputs (line 32) | type Outputs = Record<never, never>;
  type Inputs (line 44) | type Inputs = undefined;
  type Outputs (line 45) | type Outputs = undefined;
  type Inputs (line 57) | type Inputs = {
  type Outputs (line 60) | type Outputs = Record<never, never>;
  type Inputs (line 77) | type Inputs = Record<never, never>;
  type Outputs (line 78) | type Outputs = {
  type Inputs (line 135) | type Inputs = {
  type Inputs (line 155) | type Inputs = {
  type Outputs (line 160) | type Outputs = {

FILE: src/functions/unhandled-event-error.ts
  class UnhandledEventError (line 2) | class UnhandledEventError extends Error {
    method constructor (line 3) | constructor(message: string) {

FILE: src/manifest/errors.ts
  class DuplicateCallbackIdError (line 1) | class DuplicateCallbackIdError extends Error {
    method constructor (line 2) | constructor(callbackId: string, readableType: "Function" | "Workflow") {
  class DuplicateNameError (line 7) | class DuplicateNameError extends Error {
    method constructor (line 8) | constructor(
  class DuplicateProviderKeyError (line 16) | class DuplicateProviderKeyError extends Error {
    method constructor (line 17) | constructor(provider_key: string, readableType: "OAuth2Provider") {

FILE: src/manifest/manifest_schema.ts
  type ManifestSchema (line 14) | type ManifestSchema = {
  type ManifestMetadataSchema (line 34) | type ManifestMetadataSchema = {
  type ManifestSettingsSchema (line 42) | type ManifestSettingsSchema = {
  type ManifestEventSubscriptionsSchema (line 55) | type ManifestEventSubscriptionsSchema = {
  type ManifestIncomingWebhooks (line 72) | type ManifestIncomingWebhooks = {
  type ManifestInteractivitySchema (line 77) | type ManifestInteractivitySchema = {
  type ManifestSiwsLinksSchema (line 84) | type ManifestSiwsLinksSchema = {
  type ManifestFunctionRuntime (line 89) | type ManifestFunctionRuntime = "slack" | "remote" | "local";
  type ManifestAppDirectorySchema (line 94) | type ManifestAppDirectorySchema = {
  type ManifestDisplayInformationSchema (line 109) | type ManifestDisplayInformationSchema = {
  type ManifestOauthConfigSchema (line 119) | type ManifestOauthConfigSchema = {
  type ManifestFeaturesSchema (line 131) | interface ManifestFeaturesSchema {
  type ManifestBotUserSchema (line 141) | type ManifestBotUserSchema = {
  type ManifestAppHomeSchema (line 147) | type ManifestAppHomeSchema = ManifestAppHomeMessagesTabSchema & {
  type ManifestAppHomeMessagesTabSchema (line 151) | type ManifestAppHomeMessagesTabSchema = {
  type ManifestShortcutSchema (line 164) | type ManifestShortcutSchema = {
  type ManifestShortcutsSchema (line 171) | type ManifestShortcutsSchema = PopulatedArray<ManifestShortcutSchema>;
  type ManifestSlashCommandsSchema (line 174) | type ManifestSlashCommandsSchema = PopulatedArray<
  type ManifestSlashCommandSchema (line 178) | type ManifestSlashCommandSchema = {
  type ManifestWorkflowStepLegacy (line 188) | type ManifestWorkflowStepLegacy = {
  type ManifestWorkflowStepsSchemaLegacy (line 193) | type ManifestWorkflowStepsSchemaLegacy = PopulatedArray<
  type ManifestUnfurlDomainsSchema (line 198) | type ManifestUnfurlDomainsSchema = [string, ...string[]];
  type ManifestFunction (line 207) | type ManifestFunction = ISlackFunctionDefinition<any, any, any, any>;
  type ManifestFunctionsSchema (line 209) | type ManifestFunctionsSchema = { [key: string]: ManifestFunctionSchema };
  type ManifestFunctionType (line 211) | type ManifestFunctionType = "API" | "app" | undefined;
  type ManifestFunctionSchema (line 213) | type ManifestFunctionSchema = {
  type ManifestFunctionParameters (line 222) | type ManifestFunctionParameters = {
  type RequiredParameters (line 227) | type RequiredParameters = {
  type ManifestWorkflow (line 235) | type ManifestWorkflow = ISlackWorkflow;
  type ManifestWorkflowsSchema (line 237) | type ManifestWorkflowsSchema = { [key: string]: ManifestWorkflowSchema };
  type ManifestWorkflowSchema (line 238) | type ManifestWorkflowSchema = {
  type ManifestWorkflowStepSchema (line 244) | type ManifestWorkflowStepSchema = {
  type ManifestCustomEventSchema (line 256) | type ManifestCustomEventSchema = ParameterDefinition;
  type ManifestCustomEventsSchema (line 258) | type ManifestCustomEventsSchema = {
  type ManifestCustomTypeSchema (line 265) | type ManifestCustomTypeSchema = ParameterDefinition;
  type ManifestCustomTypesSchema (line 266) | type ManifestCustomTypesSchema = {
  type ManifestDatastore (line 273) | type ManifestDatastore = ISlackDatastore;
  type ManifestDatastoreSchema (line 274) | type ManifestDatastoreSchema = {
  type ManifestDataStoresSchema (line 288) | type ManifestDataStoresSchema = {
  type ManifestOAuth2Schema (line 295) | type ManifestOAuth2Schema = {
  type ManifestOAuth2ProviderSchema (line 299) | type ManifestOAuth2ProviderSchema = {
  type ManifestExternalAuthProviders (line 304) | interface ManifestExternalAuthProviders {
  type PopulatedArray (line 313) | type PopulatedArray<T> = [T, ...T[]];

FILE: src/manifest/mod.ts
  class SlackManifest (line 35) | class SlackManifest {
    method constructor (line 36) | constructor(private definition: SlackManifestType) {
    method export (line 40) | export() {
    method registerFeatures (line 147) | private registerFeatures() {
    method registerFunction (line 176) | registerFunction(func: ManifestFunction) {
    method registerTypes (line 185) | registerTypes(parameterSet: ParameterSetDefinition) {
    method registerType (line 193) | registerType(customType: ICustomType) {
    method ensureBotScopes (line 211) | private ensureBotScopes(): string[] {
    method getFunctionRuntime (line 235) | private getFunctionRuntime(): ManifestFunctionRuntime {
    method assignRemoteSlackManifestProperties (line 240) | private assignRemoteSlackManifestProperties(manifest: ManifestSchema) {
    method assignRunOnSlackManifestProperties (line 293) | private assignRunOnSlackManifestProperties(manifest: ManifestSchema) {

FILE: src/manifest/types.ts
  type SlackManifestType (line 29) | type SlackManifestType =
  type ISlackManifestRunOnSlack (line 38) | interface ISlackManifestRunOnSlack extends ISlackManifestShared {
  type ISlackManifestRemote (line 49) | interface ISlackManifestRemote extends ISlackManifestShared {
  type ISlackManifestShared (line 69) | interface ISlackManifestShared {
  type ISlackManifestRunOnSlackFeaturesSchema (line 85) | interface ISlackManifestRunOnSlackFeaturesSchema {
  type ISlackManifestRemoteFeaturesSchema (line 89) | interface ISlackManifestRemoteFeaturesSchema {

FILE: src/manifest/types_util.ts
  type Split (line 11) | type Split<
  type CamelCase (line 20) | type CamelCase<K> = K extends string ? CamelCaseStringArray<
  type CamelCaseStringArray (line 25) | type CamelCaseStringArray<Parts extends readonly string[]> = Parts extends
  type InnerCamelCaseStringArray (line 32) | type InnerCamelCaseStringArray<Parts extends readonly any[], PreviousPar...
  type CamelCasedPropertiesDeep (line 45) | type CamelCasedPropertiesDeep<Value> = Value extends Function ? Value

FILE: src/parameters/definition_types.ts
  type ParameterDefinition (line 10) | type ParameterDefinition = TypedParameterDefinition;
  type PrimitiveParameterDefinition (line 12) | type PrimitiveParameterDefinition =
  type TypedParameterDefinition (line 21) | type TypedParameterDefinition =
  type CustomTypeParameterDefinition (line 28) | interface CustomTypeParameterDefinition
  type BaseParameterDefinition (line 33) | interface BaseParameterDefinition<T> {
  type ParameterDefinitionWithGenerics (line 56) | type ParameterDefinitionWithGenerics<
  type UntypedObjectParameterDefinition (line 63) | interface UntypedObjectParameterDefinition
  type TypedObjectProperties (line 68) | type TypedObjectProperties = {
  type TypedObjectRequiredProperties (line 74) | type TypedObjectRequiredProperties<Props extends TypedObjectProperties> =
  type TypedObjectParameterDefinition (line 82) | interface TypedObjectParameterDefinition<
  type TypedObjectParameter (line 103) | type TypedObjectParameter = TypedObjectParameterDefinition<
  type BooleanParameterDefinition (line 108) | interface BooleanParameterDefinition extends BaseParameterDefinition<boo...
  type StringParameterDefinition (line 112) | interface StringParameterDefinition extends BaseParameterDefinition<stri...
  type IntegerParameterDefinition (line 126) | interface IntegerParameterDefinition extends BaseParameterDefinition<num...
  type NumberParameterDefinition (line 138) | interface NumberParameterDefinition extends BaseParameterDefinition<numb...
  type OAuth2ParameterDefinition (line 150) | interface OAuth2ParameterDefinition extends BaseParameterDefinition<stri...
  type EnumChoice (line 158) | type EnumChoice<T> = {
  type UntypedArrayParameterDefinition (line 167) | interface UntypedArrayParameterDefinition
  type TypedArrayParameterDefinition (line 176) | interface TypedArrayParameterDefinition
  type AllValues (line 182) | type AllValues = AllPrimitiveValues | ObjectValue | ArrayValue;
  type AllPrimitiveValues (line 184) | type AllPrimitiveValues = string | number | boolean;
  type ObjectValue (line 186) | type ObjectValue = {
  type ArrayValue (line 190) | type ArrayValue = AllPrimitiveValues[];

FILE: src/parameters/types.ts
  type ParameterSetDefinition (line 15) | type ParameterSetDefinition = {
  type PossibleParameterKeys (line 19) | type PossibleParameterKeys<
  type ParameterPropertiesDefinition (line 23) | type ParameterPropertiesDefinition<
  type ParameterVariableType (line 31) | type ParameterVariableType<
  type SingleParameterVariable (line 47) | type SingleParameterVariable = {};
  type UntypedObjectParameterVariableType (line 50) | type UntypedObjectParameterVariableType = any;
  type ObjectParameterPropertyTypes (line 52) | type ObjectParameterPropertyTypes<
  type ObjectParameterVariableType (line 62) | type ObjectParameterVariableType<

FILE: src/providers/oauth2/mod.ts
  class OAuth2Provider (line 15) | class OAuth2Provider {
    method constructor (line 20) | constructor(
    method export (line 28) | export(): ManifestOAuth2ProviderSchema {

FILE: src/providers/oauth2/types.ts
  type IdentityUrlHttpMethodTypes (line 6) | type IdentityUrlHttpMethodTypes = "GET" | "POST";
  type OAuth2ProviderIdentitySchema (line 7) | type OAuth2ProviderIdentitySchema = {
  type tokenUrlConfigSchema (line 26) | type tokenUrlConfigSchema = {
  type OAuth2ProviderOptions (line 31) | type OAuth2ProviderOptions = {
  type OAuth2ProviderDefinitionArgs (line 53) | type OAuth2ProviderDefinitionArgs = {

FILE: src/schema/providers/oauth2/types.ts
  type OAuth2ProviderTypeValues (line 3) | type OAuth2ProviderTypeValues =

FILE: src/schema/schema_types.ts
  type ValidSchemaTypes (line 10) | type ValidSchemaTypes = typeof SchemaTypes[keyof typeof SchemaTypes];

FILE: src/schema/slack/functions/_scripts/src/templates/template_function.ts
  type AllowedHiddenParamsMap (line 20) | type AllowedHiddenParamsMap = Record<
  function manifestFunctionFieldsToTypeScript (line 142) | function manifestFunctionFieldsToTypeScript(
  function SlackFunctionTemplate (line 192) | function SlackFunctionTemplate(

FILE: src/schema/slack/functions/_scripts/src/templates/test_template.ts
  function SlackTestFunctionTemplate (line 115) | function SlackTestFunctionTemplate(

FILE: src/schema/slack/functions/_scripts/src/templates/types.ts
  type AllowedTypeValue (line 3) | type AllowedTypeValue = ICustomType | string;
  type AllowedTypeValueObject (line 4) | type AllowedTypeValueObject = Record<string, AllowedTypeValue>;

FILE: src/schema/slack/functions/_scripts/src/templates/utils.ts
  function autogeneratedComment (line 10) | function autogeneratedComment(includeDate?: boolean): string {
  function renderFunctionImport (line 15) | function renderFunctionImport(callbackId: string): string {
  function getFunctionName (line 19) | function getFunctionName(callbackId: string): string {
  function getSlackCallbackId (line 23) | function getSlackCallbackId(
  function getParameterType (line 29) | function getParameterType(type: AllowedTypeValue): string {
  function renderTypeImports (line 68) | function renderTypeImports(functionRecord: FunctionRecord) {
  function sanitize (line 85) | function sanitize(value: string): string {

FILE: src/schema/slack/functions/_scripts/src/templates/utils_test.ts
  constant DESCRIPTION (line 18) | const DESCRIPTION = "Test the Slack function template";
  constant TITLE (line 19) | const TITLE = "test function";
  constant CALLBACK_ID (line 20) | const CALLBACK_ID = "test_function";
  constant SLACK_FUNCTION_TYPE (line 21) | const SLACK_FUNCTION_TYPE = "builtin";

FILE: src/schema/slack/functions/_scripts/src/types.ts
  type BaseFunctionProperty (line 1) | type BaseFunctionProperty = {
  type ObjectFunctionProperty (line 7) | type ObjectFunctionProperty = BaseFunctionProperty & {
  type ArrayFunctionProperty (line 13) | type ArrayFunctionProperty = BaseFunctionProperty & {
  type FunctionProperty (line 17) | type FunctionProperty =
  type FunctionProperties (line 22) | type FunctionProperties = {
  type FunctionParameter (line 26) | type FunctionParameter = FunctionProperty & {
  type FunctionRecord (line 32) | type FunctionRecord = {
  type FunctionsPayload (line 42) | type FunctionsPayload = {

FILE: src/schema/slack/functions/_scripts/src/utils.ts
  constant FUNCTIONS_JSON_PATH (line 9) | const FUNCTIONS_JSON_PATH = "functions.json";
  constant FUNCTIONS_TO_IGNORE (line 21) | const FUNCTIONS_TO_IGNORE = [
  function getSlackFunctions (line 30) | async function getSlackFunctions(
  function isObjectFunctionProperty (line 42) | function isObjectFunctionProperty(
  function isArrayFunctionProperty (line 48) | function isArrayFunctionProperty(

FILE: src/schema/slack/functions/_scripts/src/write_function_files.ts
  constant VALID_FILENAME_REGEX (line 9) | const VALID_FILENAME_REGEX = /^[0-9a-zA-Z_\-]+$/;
  function main (line 11) | async function main() {

FILE: src/schema/slack/types/mod.ts
  type ValidSlackPrimitiveTypes (line 21) | type ValidSlackPrimitiveTypes =

FILE: src/schema/types.ts
  type BaseSchemaType (line 1) | type BaseSchemaType = {
  type SchemaType (line 8) | type SchemaType = BaseSchemaType & {

FILE: src/test_utils.ts
  type IsAny (line 4) | type IsAny<T> = unknown extends T ? T extends {} ? T : never : never;
  type NotAny (line 5) | type NotAny<T> = T extends IsAny<T> ? never : T;
  type CanBe (line 17) | type CanBe<T, U> = Extract<T, U> extends never ? false : true;
  type CannotBe (line 21) | type CannotBe<T, U> = Extract<T, U> extends never ? true : false;
  type CanBeUndefined (line 26) | type CanBeUndefined<T> = CanBe<T, undefined> extends true ? true
  type CannotBeUndefined (line 28) | type CannotBeUndefined<T> = CannotBe<T, undefined> extends true ? true

FILE: src/test_utils_test.ts
  type T (line 86) | type T = {

FILE: src/type_utils.ts
  type RecursionDepthLevel (line 2) | type RecursionDepthLevel = 0 | 1 | 2 | 3 | 4 | 5;
  type MaxRecursionDepth (line 5) | type MaxRecursionDepth = 5;
  type IncreaseDepth (line 8) | type IncreaseDepth<Depth extends RecursionDepthLevel = 0> = Depth extends
  type LooseStringAutocomplete (line 19) | type LooseStringAutocomplete<T> = T | (string & {});

FILE: src/types.ts
  type InvocationPayload (line 7) | type InvocationPayload<Body> = {
  type Env (line 19) | type Env = Record<string, string>;

FILE: src/types/mod.ts
  function DefineType (line 14) | function DefineType<
  class CustomType (line 24) | class CustomType<
    method constructor (line 33) | constructor(
    method generateReferenceString (line 42) | private generateReferenceString() {
    method toString (line 46) | toString() {
    method toJSON (line 49) | toJSON() {
    method registerParameterTypes (line 53) | registerParameterTypes(manifest: SlackManifest) {
    method export (line 69) | export(): ManifestCustomTypeSchema {

FILE: src/types/types.ts
  type CustomTypeDefinition (line 9) | type CustomTypeDefinition<
  type ICustomType (line 16) | interface ICustomType<

FILE: src/workflows/mod.ts
  class WorkflowDefinition (line 41) | class WorkflowDefinition<
    method constructor (line 69) | constructor(
    method addStep (line 145) | addStep<
    method export (line 190) | export(): ManifestWorkflowSchema {
    method registerStepFunctions (line 199) | registerStepFunctions(manifest: SlackManifest) {
    method registerParameterTypes (line 203) | registerParameterTypes(manifest: SlackManifest) {
    method toJSON (line 210) | toJSON() {

FILE: src/workflows/types.ts
  type ISlackWorkflow (line 10) | interface ISlackWorkflow {
  type SlackWorkflowDefinition (line 17) | type SlackWorkflowDefinition<Definition> = Definition extends
  type SlackWorkflowDefinitionArgs (line 22) | type SlackWorkflowDefinitionArgs<
  type WorkflowInputs (line 42) | type WorkflowInputs<
  type WorkflowOutputs (line 47) | type WorkflowOutputs<
  type WorkflowStepOutputs (line 52) | type WorkflowStepOutputs<
  type WorkflowParameterReferences (line 57) | type WorkflowParameterReferences<
  type WorkflowStepInputs (line 71) | type WorkflowStepInputs<

FILE: src/workflows/workflow-step.ts
  type WorkflowStepDefinition (line 20) | type WorkflowStepDefinition =
  method constructor (line 32) | constructor(
  method templatizeInputs (line 45) | templatizeInputs() {
  method export (line 60) | export(): ManifestWorkflowStepSchema {
  method toJSON (line 68) | toJSON() {
  method registerFunction (line 72) | registerFunction(_manifest: SlackManifest) {
  method isLocalFunctionReference (line 76) | protected isLocalFunctionReference(): boolean {
  class TypedWorkflowStepDefinition (line 81) | class TypedWorkflowStepDefinition<
    method constructor (line 99) | constructor(
    method registerFunction (line 138) | override registerFunction(manifest: SlackManifest) {
  class UntypedWorkflowStepDefinition (line 145) | class UntypedWorkflowStepDefinition extends BaseWorkflowStepDefinition {
    method constructor (line 148) | constructor(
Condensed preview — 194 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (684K chars).
[
  {
    "path": ".github/CODEOWNERS",
    "chars": 389,
    "preview": "# Salesforce Open Source project configuration\n# Learn more: https://github.com/salesforce/oss-template\n#ECCN:Open Sourc"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "chars": 3725,
    "preview": "# Contributors Guide\n\nInterested in contributing? Awesome! Before you do though, please read our\n[Code of Conduct](https"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug.md",
    "chars": 1187,
    "preview": "---\nname: Bug Report\nabout: Report a bug encountered while using this project\ntitle: '[BUG] <title>'\n---\n\n<!-- If you fi"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature.md",
    "chars": 771,
    "preview": "---\nname: Feature request\nabout: Suggest a new feature for this project\ntitle: '[FEATURE] <title>'\n---\n\n<!-- If you have"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/question.md",
    "chars": 942,
    "preview": "---\nname: Question\nabout: Ask a question about this project\ntitle: '[QUERY] <title>'\nlabel: question\n---\n\n<!-- If you ha"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "chars": 893,
    "preview": "<!--\nThanks for sending a pull request!\n\nFriendly reminder: this project is open sourced, the internet can see it,\nmake "
  },
  {
    "path": ".github/dependabot.yml",
    "chars": 310,
    "preview": "# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependa"
  },
  {
    "path": ".github/maintainers_guide.md",
    "chars": 8928,
    "preview": "# Maintainers Guide\n\nThis document describes tools, tasks and workflow that one needs to be familiar\nwith in order to ef"
  },
  {
    "path": ".github/workflows/deno.yml",
    "chars": 1053,
    "preview": "name: Deno\n\non:\n  push:\n    branches:\n      - main\n  pull_request:\n    branches:\n      - main\n\njobs:\n  deno:\n    runs-on"
  },
  {
    "path": ".github/workflows/dependencies.yml",
    "chars": 1163,
    "preview": "name: Merge updates to dependencies\non:\n  pull_request:\njobs:\n  dependabot:\n    name: \"@dependabot\"\n    if: github.event"
  },
  {
    "path": ".github/workflows/e2e.yml",
    "chars": 3781,
    "preview": "# This workflow invokes and waits for the result of Slack's private E2E CI system\nname: Internal E2E CI\n\non:\n  push:\n   "
  },
  {
    "path": ".github/workflows/npm-publish.yml",
    "chars": 1326,
    "preview": "# This workflow will run tests using node and then publish a package to GitHub Packages when a release is created\n# For "
  },
  {
    "path": ".github/workflows/npm.yml",
    "chars": 1020,
    "preview": "# This workflow runs a test build for npm against changes on main or PRs\n\nname: Npm Build\n\non:\n  push:\n    branches:\n   "
  },
  {
    "path": ".github/workflows/publish.yml",
    "chars": 421,
    "preview": "name: Publish\n\non:\n  push:\n    tags:\n      - '[0-9]+.[0-9]+.[0-9]+'\n\njobs:\n  publish:\n    runs-on: ubuntu-latest\n    per"
  },
  {
    "path": ".github/workflows/samples.yml",
    "chars": 1763,
    "preview": "# This workflow runs a `deno check` against slack sample apps\nname: Samples Integration Type-checking\n\non:\n  push:\n    b"
  },
  {
    "path": ".gitignore",
    "chars": 40,
    "preview": ".DS_Store\n.coverage\n.vim\nlcov.info\nnpm/\n"
  },
  {
    "path": ".vscode/settings.json",
    "chars": 192,
    "preview": "{\n  \"deno.enable\": true,\n  \"deno.lint\": true,\n  \"deno.config\": \"./deno.jsonc\",\n  \"[typescript]\": {\n    \"editor.formatOnS"
  },
  {
    "path": "LICENSE",
    "chars": 1076,
    "preview": "MIT License\n\nCopyright (c) Slack Technologies, Inc.\n\nPermission is hereby granted, free of charge, to any person obtaini"
  },
  {
    "path": "README.md",
    "chars": 6838,
    "preview": "<h1 align=\"center\">\n  Deno Slack SDK\n  <br>\n</h1>\n\n<p align=\"center\">\n    <img alt=\"deno.land version\" src=\"https://img."
  },
  {
    "path": "deno.jsonc",
    "chars": 1435,
    "preview": "{\n  \"$schema\": \"https://deno.land/x/deno/cli/schemas/config-file.v1.json\",\n  \"name\": \"@slack/sdk\",\n  \"version\": \"2.15.2\""
  },
  {
    "path": "docs/datastores.md",
    "chars": 2077,
    "preview": "## Datastores\n\n### Defining a Datastore\n\nDatastores can be defined with the top level `DefineDatastore` export. Below is"
  },
  {
    "path": "docs/events.md",
    "chars": 4321,
    "preview": "## Events\n\nCustom events provide a way for Apps to validate\n[message metadata](https://api.slack.com/metadata) against a"
  },
  {
    "path": "docs/functions-action-handlers.md",
    "chars": 10373,
    "preview": "## Block kit action handlers\n\nYour application's [functions][functions] can do a wide variety of interesting\nthings: pos"
  },
  {
    "path": "docs/functions-suggestion-handlers.md",
    "chars": 10008,
    "preview": "## Block Kit suggestion handlers\n\nYour application's [functions][functions] can do a wide variety of interesting\nthings:"
  },
  {
    "path": "docs/functions-view-handlers.md",
    "chars": 14199,
    "preview": "## View Handlers\n\nYour application's [functions][functions] can do a wide variety of interesting\nthings: post messages, "
  },
  {
    "path": "docs/functions.md",
    "chars": 7133,
    "preview": "## Custom functions\n\nFunctions are the core of your Slack app: they accept one or more input\nparameters, execute some lo"
  },
  {
    "path": "docs/manifest.md",
    "chars": 2014,
    "preview": "## Manifest\n\nA Manifest defines your entire Slack application, from its core properties like\nits name and description to"
  },
  {
    "path": "docs/types.md",
    "chars": 1708,
    "preview": "## Types\n\nCustom Types provide a way to introduce reusable, sharable types to Apps.\n\n### Defining a type\n\nTypes can be d"
  },
  {
    "path": "docs/workflows.md",
    "chars": 3713,
    "preview": "## Workflows\n\nWorkflows can be defined and included in your [manifest][manifest]. A workflow\nitself has several pieces o"
  },
  {
    "path": "scripts/build_npm.ts",
    "chars": 1290,
    "preview": "// ex. scripts/build_npm.ts\nimport { build, emptyDir } from \"jsr:@deno/dnt@0.42.1\";\n\nawait emptyDir(\"./npm\");\n\nawait bui"
  },
  {
    "path": "scripts/bundle.ts",
    "chars": 1473,
    "preview": "import * as esbuild from \"npm:esbuild@0.25.5\";\nimport { denoPlugins } from \"jsr:@luca/esbuild-deno-loader@0.11.1\";\nimpor"
  },
  {
    "path": "scripts/imports/update.ts",
    "chars": 958,
    "preview": "import { parseArgs } from \"jsr:@std/cli@1.0.17/parse-args\";\nimport { dirname, join, relative } from \"jsr:@std/path@1.1.0"
  },
  {
    "path": "src/README.md",
    "chars": 7012,
    "preview": "<h1 align=\"center\">\n  Deno Slack SDK\n  <br>\n</h1>\n\n<p align=\"center\">\n    <img alt=\"deno.land version\" src=\"https://img."
  },
  {
    "path": "src/datastore/datastore_test.ts",
    "chars": 1395,
    "preview": "import { assertStrictEquals } from \"../dev_deps.ts\";\nimport { DefineDatastore } from \"./mod.ts\";\nimport SchemaTypes from"
  },
  {
    "path": "src/datastore/mod.ts",
    "chars": 1814,
    "preview": "import type { SlackManifest } from \"../manifest/mod.ts\";\nimport type { ManifestDatastoreSchema } from \"../manifest/manif"
  },
  {
    "path": "src/datastore/types.ts",
    "chars": 1973,
    "preview": "import type { ICustomType } from \"../types/types.ts\";\nimport type { ManifestDatastoreSchema } from \"../manifest/manifest"
  },
  {
    "path": "src/deps.ts",
    "chars": 124,
    "preview": "export { SlackAPI } from \"jsr:@slack/api@2.9.0\";\nexport type { SlackAPIClient, Trigger } from \"jsr:@slack/api@2.9.0/type"
  },
  {
    "path": "src/dev_deps.ts",
    "chars": 471,
    "preview": "export {\n  assertEquals,\n  assertExists,\n  assertInstanceOf,\n  AssertionError,\n  assertMatch,\n  assertNotStrictEquals,\n "
  },
  {
    "path": "src/events/events_test.ts",
    "chars": 1150,
    "preview": "import { DefineEvent } from \"./mod.ts\";\nimport { assertEquals } from \"../dev_deps.ts\";\nimport { DefineType, Schema } fro"
  },
  {
    "path": "src/events/mod.ts",
    "chars": 1767,
    "preview": "import type { SlackManifest } from \"../manifest/mod.ts\";\nimport type { ManifestCustomEventSchema } from \"../manifest/man"
  },
  {
    "path": "src/events/types.ts",
    "chars": 877,
    "preview": "import type {\n  CustomTypeParameterDefinition,\n  TypedObjectParameter,\n} from \"../parameters/definition_types.ts\";\nimpor"
  },
  {
    "path": "src/functions/definitions/connector-function.ts",
    "chars": 1805,
    "preview": "import type { ManifestFunctionType } from \"../../manifest/manifest_schema.ts\";\nimport type {\n  ParameterSetDefinition,\n "
  },
  {
    "path": "src/functions/definitions/connector-function_test.ts",
    "chars": 2375,
    "preview": "import {\n  assertEquals,\n  assertInstanceOf,\n  assertStrictEquals,\n} from \"../../dev_deps.ts\";\nimport type { PossiblePar"
  },
  {
    "path": "src/functions/definitions/mod.ts",
    "chars": 172,
    "preview": "export { DefineFunction, SlackFunctionDefinition } from \"./slack-function.ts\";\nexport {\n  ConnectorFunctionDefinition,\n "
  },
  {
    "path": "src/functions/definitions/slack-function.ts",
    "chars": 3286,
    "preview": "import type {\n  ManifestFunctionSchema,\n  ManifestFunctionType,\n} from \"../../manifest/manifest_schema.ts\";\nimport type "
  },
  {
    "path": "src/functions/definitions/slack-function_test.ts",
    "chars": 7934,
    "preview": "import {\n  assertEquals,\n  assertInstanceOf,\n  assertStrictEquals,\n} from \"../../dev_deps.ts\";\nimport Schema from \"../.."
  },
  {
    "path": "src/functions/enrich-context.ts",
    "chars": 535,
    "preview": "import { SlackAPI } from \"../deps.ts\";\nimport type {\n  BaseRuntimeFunctionContext,\n  FunctionContextEnrichment,\n} from \""
  },
  {
    "path": "src/functions/enrich-context_test.ts",
    "chars": 880,
    "preview": "import { assertExists } from \"../dev_deps.ts\";\nimport { enrichContext } from \"./enrich-context.ts\";\nimport type { BaseRu"
  },
  {
    "path": "src/functions/interactivity/block_actions_router.ts",
    "chars": 6760,
    "preview": "import type {\n  ParameterSetDefinition,\n  PossibleParameterKeys,\n} from \"../../parameters/types.ts\";\nimport type { Slack"
  },
  {
    "path": "src/functions/interactivity/block_actions_router_test.ts",
    "chars": 22704,
    "preview": "import { SlackAPI } from \"../../deps.ts\";\nimport {\n  assertEquals,\n  assertExists,\n  assertRejects,\n  mock,\n} from \"../."
  },
  {
    "path": "src/functions/interactivity/block_actions_types.ts",
    "chars": 6243,
    "preview": "/*\nThis is currently a pretty light type of a block_actions payload that we can fill out\nonce we have a way to share som"
  },
  {
    "path": "src/functions/interactivity/block_kit_types.ts",
    "chars": 509,
    "preview": "/**\n * @description A single Block Kit interactive component interaction.\n */\nexport type BlockAction =\n  & {\n    /**\n  "
  },
  {
    "path": "src/functions/interactivity/block_suggestion_router.ts",
    "chars": 7042,
    "preview": "import type {\n  ParameterSetDefinition,\n  PossibleParameterKeys,\n} from \"../../parameters/types.ts\";\nimport type { Slack"
  },
  {
    "path": "src/functions/interactivity/block_suggestion_router_test.ts",
    "chars": 23405,
    "preview": "import { SlackAPI } from \"../../deps.ts\";\nimport {\n  assertEquals,\n  assertExists,\n  assertRejects,\n  mock,\n} from \"../."
  },
  {
    "path": "src/functions/interactivity/block_suggestion_types.ts",
    "chars": 5817,
    "preview": "import type { BlockAction, BlockElement } from \"./block_kit_types.ts\";\n\n// TODO: lots of duplication here with block_act"
  },
  {
    "path": "src/functions/interactivity/matchers.ts",
    "chars": 868,
    "preview": "import type {\n  BasicConstraintField,\n  BlockActionConstraintObject,\n} from \"./types.ts\";\nimport type { BlockAction } fr"
  },
  {
    "path": "src/functions/interactivity/mod.ts",
    "chars": 182,
    "preview": "export { BlockActionsRouter } from \"./block_actions_router.ts\";\nexport { ViewsRouter } from \"./view_router.ts\";\nexport {"
  },
  {
    "path": "src/functions/interactivity/types.ts",
    "chars": 8578,
    "preview": "import type {\n  BaseRuntimeFunctionContext,\n  FunctionContextEnrichment,\n  FunctionDefinitionArgs,\n  FunctionParameters,"
  },
  {
    "path": "src/functions/interactivity/view_router.ts",
    "chars": 6637,
    "preview": "import type {\n  ParameterSetDefinition,\n  PossibleParameterKeys,\n} from \"../../parameters/types.ts\";\nimport type { Slack"
  },
  {
    "path": "src/functions/interactivity/view_router_test.ts",
    "chars": 15912,
    "preview": "import { SlackAPI } from \"../../deps.ts\";\nimport {\n  assertEquals,\n  assertExists,\n  assertRejects,\n  mock,\n} from \"../."
  },
  {
    "path": "src/functions/interactivity/view_types.ts",
    "chars": 5215,
    "preview": "/*\nPossibly helpful links:\n- https://github.com/slackapi/bolt-js/blob/main/src/types/view/index.ts\n- https://api.slack.c"
  },
  {
    "path": "src/functions/mod.ts",
    "chars": 72,
    "preview": "export { DefineConnector, DefineFunction } from \"./definitions/mod.ts\";\n"
  },
  {
    "path": "src/functions/slack-function.ts",
    "chars": 3801,
    "preview": "import type {\n  ParameterSetDefinition,\n  PossibleParameterKeys,\n} from \"../parameters/types.ts\";\nimport type {\n  Enrich"
  },
  {
    "path": "src/functions/slack-function_test.ts",
    "chars": 5916,
    "preview": "import { assertEquals, assertExists, mock } from \"../dev_deps.ts\";\nimport { DefineFunction, SlackFunction } from \"../mod"
  },
  {
    "path": "src/functions/tester/function_tester_test.ts",
    "chars": 3655,
    "preview": "import { assertEquals, mock } from \"../../dev_deps.ts\";\nimport { DEFAULT_FUNCTION_TESTER_TITLE, SlackFunctionTester } fr"
  },
  {
    "path": "src/functions/tester/mod.ts",
    "chars": 2320,
    "preview": "import { SlackAPI } from \"../../deps.ts\";\nimport type {\n  ParameterSetDefinition,\n  PossibleParameterKeys,\n} from \"../.."
  },
  {
    "path": "src/functions/tester/types.ts",
    "chars": 1852,
    "preview": "import type {\n  ParameterSetDefinition,\n  PossibleParameterKeys,\n} from \"../../parameters/types.ts\";\nimport type { Slack"
  },
  {
    "path": "src/functions/types.ts",
    "chars": 14521,
    "preview": "import type { SlackAPIClient } from \"../deps.ts\";\nimport type { Env } from \"../types.ts\";\nimport type {\n  ManifestFuncti"
  },
  {
    "path": "src/functions/types_base_runtime_function_handler_test.ts",
    "chars": 5189,
    "preview": "import { assertEquals } from \"../dev_deps.ts\";\nimport { SlackFunctionTester } from \"./tester/mod.ts\";\nimport type { Base"
  },
  {
    "path": "src/functions/types_runtime_slack_function_handler_test.ts",
    "chars": 1109,
    "preview": "import { assertExists } from \"../dev_deps.ts\";\nimport { assertEqualsTypedValues } from \"../test_utils.ts\";\nimport { Slac"
  },
  {
    "path": "src/functions/unhandled-event-error.ts",
    "chars": 314,
    "preview": "// This error conforms with what the deno-slack-runtime expects for an unhandled events\nexport class UnhandledEventError"
  },
  {
    "path": "src/manifest/errors.ts",
    "chars": 663,
    "preview": "export class DuplicateCallbackIdError extends Error {\n  constructor(callbackId: string, readableType: \"Function\" | \"Work"
  },
  {
    "path": "src/manifest/errors_test.ts",
    "chars": 975,
    "preview": "import {\n  DuplicateCallbackIdError,\n  DuplicateNameError,\n  DuplicateProviderKeyError,\n} from \"./errors.ts\";\nimport { a"
  },
  {
    "path": "src/manifest/manifest_schema.ts",
    "chars": 10028,
    "preview": "import type { ISlackDatastore } from \"../datastore/types.ts\";\nimport type { ISlackFunctionDefinition } from \"../function"
  },
  {
    "path": "src/manifest/manifest_test.ts",
    "chars": 33945,
    "preview": "import type {\n  ISlackManifestRemote,\n  ISlackManifestRunOnSlack,\n  SlackManifestType,\n} from \"./types.ts\";\nimport { Man"
  },
  {
    "path": "src/manifest/mod.ts",
    "chars": 10899,
    "preview": "import type {\n  ISlackManifestRemote,\n  ISlackManifestRunOnSlack,\n  SlackManifestType,\n} from \"./types.ts\";\nimport type "
  },
  {
    "path": "src/manifest/types.ts",
    "chars": 3248,
    "preview": "import type {\n  ManifestAppDirectorySchema,\n  ManifestAppHomeMessagesTabSchema,\n  ManifestAppHomeSchema,\n  ManifestBotUs"
  },
  {
    "path": "src/manifest/types_util.ts",
    "chars": 1698,
    "preview": "/** Utility types to enable conversion of type properties to camelCase\n *\n * This is a shameless lift from sindresorhus'"
  },
  {
    "path": "src/mod.ts",
    "chars": 1014,
    "preview": "export { Manifest, SlackManifest } from \"./manifest/mod.ts\";\nexport type {\n  ISlackManifestRemote,\n  ISlackManifestRunOn"
  },
  {
    "path": "src/mod_test.ts",
    "chars": 173,
    "preview": "import { assertExists } from \"./dev_deps.ts\";\nimport * as mod from \"./mod.ts\";\n\nDeno.test(\"Include all content of mod.ts"
  },
  {
    "path": "src/parameters/define_property.ts",
    "chars": 405,
    "preview": "import type {\n  TypedObjectParameterDefinition,\n  TypedObjectProperties,\n  TypedObjectRequiredProperties,\n} from \"../par"
  },
  {
    "path": "src/parameters/define_property_test.ts",
    "chars": 1170,
    "preview": "import { DefineProperty } from \"./define_property.ts\";\nimport SchemaTypes from \"../schema/schema_types.ts\";\nimport { ass"
  },
  {
    "path": "src/parameters/definition_types.ts",
    "chars": 7740,
    "preview": "import type SchemaTypes from \"../schema/schema_types.ts\";\nimport type { ValidSchemaTypes } from \"../schema/schema_types."
  },
  {
    "path": "src/parameters/mod.ts",
    "chars": 3196,
    "preview": "// import SchemaTypes from \"../schema/schema_types.ts\";\nimport type {\n  ObjectParameterVariableType,\n  ParameterVariable"
  },
  {
    "path": "src/parameters/param.ts",
    "chars": 257,
    "preview": "// deno-lint-ignore no-explicit-any\nexport const ParamReference = (...path: (string | undefined)[]): any => {\n  const fu"
  },
  {
    "path": "src/parameters/param_test.ts",
    "chars": 672,
    "preview": "import { assertEquals } from \"../dev_deps.ts\";\nimport { ParamReference } from \"./param.ts\";\n\nDeno.test(ParamReference.na"
  },
  {
    "path": "src/parameters/parameter-variable_test.ts",
    "chars": 9870,
    "preview": "import SchemaTypes from \"../schema/schema_types.ts\";\nimport { ParameterVariable } from \"./mod.ts\";\nimport type { SingleP"
  },
  {
    "path": "src/parameters/types.ts",
    "chars": 2200,
    "preview": "import type {\n  IncreaseDepth,\n  MaxRecursionDepth,\n  RecursionDepthLevel,\n} from \"../type_utils.ts\";\nimport type {\n  Cu"
  },
  {
    "path": "src/parameters/with-untyped-object-proxy.ts",
    "chars": 1034,
    "preview": "import { ParamReference } from \"./param.ts\";\n\nexport const WithUntypedObjectProxy = (\n  // deno-lint-ignore no-explicit-"
  },
  {
    "path": "src/parameters/with-untyped-object-proxy_test.ts",
    "chars": 976,
    "preview": "import { WithUntypedObjectProxy } from \"./with-untyped-object-proxy.ts\";\nimport { assertStrictEquals } from \"../dev_deps"
  },
  {
    "path": "src/providers/oauth2/mod.ts",
    "chars": 911,
    "preview": "import type {\n  OAuth2ProviderDefinitionArgs,\n  OAuth2ProviderOptions,\n} from \"./types.ts\";\n\nimport type { OAuth2Provide"
  },
  {
    "path": "src/providers/oauth2/oauth2_test.ts",
    "chars": 12036,
    "preview": "import type { SlackManifestType } from \"../../manifest/types.ts\";\nimport { Manifest, SlackManifest } from \"../../manifes"
  },
  {
    "path": "src/providers/oauth2/types.ts",
    "chars": 2561,
    "preview": "import type {\n  OAuth2ProviderTypeValues,\n} from \"../../schema/providers/oauth2/types.ts\";\n\n/** Http Method types that a"
  },
  {
    "path": "src/schema/mod.ts",
    "chars": 325,
    "preview": "import SchemaTypes from \"./schema_types.ts\";\nimport SlackSchema from \"./slack/mod.ts\";\nimport Providers from \"./provider"
  },
  {
    "path": "src/schema/providers/mod.ts",
    "chars": 120,
    "preview": "import OAuth2Types from \"./oauth2/mod.ts\";\n\nconst Schema = {\n  oauth2: OAuth2Types,\n} as const;\n\nexport default Schema;\n"
  },
  {
    "path": "src/schema/providers/oauth2/mod.ts",
    "chars": 87,
    "preview": "const ProviderTypes = {\n  CUSTOM: \"CUSTOM\",\n} as const;\n\nexport default ProviderTypes;\n"
  },
  {
    "path": "src/schema/providers/oauth2/types.ts",
    "chars": 153,
    "preview": "import type OAuth2ProviderTypes from \"./mod.ts\";\n\nexport type OAuth2ProviderTypeValues =\n  typeof OAuth2ProviderTypes[ke"
  },
  {
    "path": "src/schema/schema_types.ts",
    "chars": 263,
    "preview": "const SchemaTypes = {\n  string: \"string\",\n  boolean: \"boolean\",\n  integer: \"integer\",\n  number: \"number\",\n  object: \"obj"
  },
  {
    "path": "src/schema/slack/functions/_scripts/.gitignore",
    "chars": 14,
    "preview": "functions.json"
  },
  {
    "path": "src/schema/slack/functions/_scripts/README.md",
    "chars": 1374,
    "preview": "# Generating Slack function source files\n\nThis script will generate the necessary function TypeScript files along with\nt"
  },
  {
    "path": "src/schema/slack/functions/_scripts/generate",
    "chars": 767,
    "preview": "#!/bin/bash\nset -euo pipefail\ncd \"$(dirname \"$0\")\"\n\n# Clean parent directory of all files ending in .ts\necho \"Cleaning f"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/templates/mod.ts",
    "chars": 190,
    "preview": "export { SlackFunctionTemplate } from \"./template_function.ts\";\nexport { SlackTestFunctionTemplate } from \"./test_templa"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/templates/template_function.ts",
    "chars": 5740,
    "preview": "import { isCustomType } from \"../../../../../../types/mod.ts\";\nimport SchemaTypes from \"../../../../../schema_types.ts\";"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/templates/template_mod.ts",
    "chars": 833,
    "preview": "import {\n  autogeneratedComment,\n  getFunctionName,\n  renderFunctionImport,\n} from \"./utils.ts\";\nimport type { FunctionR"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/templates/test_template.ts",
    "chars": 4825,
    "preview": "import {\n  autogeneratedComment,\n  getFunctionName,\n  getSlackCallbackId,\n  renderFunctionImport,\n  renderTypeImports,\n}"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/templates/types.ts",
    "chars": 194,
    "preview": "import type { ICustomType } from \"../../../../../../types/types.ts\";\n\nexport type AllowedTypeValue = ICustomType | strin"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/templates/utils.ts",
    "chars": 2949,
    "preview": "import { toPascalCase } from \"../../../../../../dev_deps.ts\";\nimport type { FunctionProperty, FunctionRecord } from \"../"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/templates/utils_test.ts",
    "chars": 6189,
    "preview": "import {\n  autogeneratedComment,\n  getFunctionName,\n  getSlackCallbackId,\n  renderFunctionImport,\n  renderTypeImports,\n "
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/test/data/function.json",
    "chars": 3697,
    "preview": "{\n  \"ok\": true,\n  \"functions\": [\n    {\n      \"id\": \"Fn0102\",\n      \"callback_id\": \"send_message\",\n      \"title\": \"Send a"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/types.ts",
    "chars": 946,
    "preview": "type BaseFunctionProperty = {\n  type: string;\n  description?: string;\n  title?: string;\n};\n\nexport type ObjectFunctionPr"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/utils.ts",
    "chars": 1385,
    "preview": "import type {\n  ArrayFunctionProperty,\n  FunctionProperty,\n  FunctionRecord,\n  FunctionsPayload,\n  ObjectFunctionPropert"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/utils_test.ts",
    "chars": 1697,
    "preview": "import {\n  getSlackFunctions,\n  greenText,\n  isArrayFunctionProperty,\n  isObjectFunctionProperty,\n  redText,\n  yellowTex"
  },
  {
    "path": "src/schema/slack/functions/_scripts/src/write_function_files.ts",
    "chars": 2193,
    "preview": "import {\n  SlackFunctionModTemplate,\n  SlackFunctionTemplate,\n  SlackTestFunctionTemplate,\n} from \"./templates/mod.ts\";\n"
  },
  {
    "path": "src/schema/slack/functions/add_bookmark.ts",
    "chars": 1427,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/add_bookmark_test.ts",
    "chars": 3079,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/add_pin.ts",
    "chars": 864,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/add_pin_test.ts",
    "chars": 1806,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/add_reaction.ts",
    "chars": 867,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/add_reaction_test.ts",
    "chars": 1883,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/add_user_to_usergroup.ts",
    "chars": 1131,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/add_user_to_usergroup_test.ts",
    "chars": 2740,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/archive_channel.ts",
    "chars": 800,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/archive_channel_test.ts",
    "chars": 2243,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/canvas_copy.ts",
    "chars": 1241,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/canvas_copy_test.ts",
    "chars": 2790,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/canvas_create.ts",
    "chars": 1554,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/canvas_create_test.ts",
    "chars": 3064,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/canvas_update_content.ts",
    "chars": 1585,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/canvas_update_content_test.ts",
    "chars": 3220,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/channel_canvas_create.ts",
    "chars": 1444,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/channel_canvas_create_test.ts",
    "chars": 3015,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/create_channel.ts",
    "chars": 1565,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/create_channel_test.ts",
    "chars": 3084,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/create_usergroup.ts",
    "chars": 1201,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/create_usergroup_test.ts",
    "chars": 2807,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/delay.ts",
    "chars": 688,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/delay_test.ts",
    "chars": 1587,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/invite_user_to_channel.ts",
    "chars": 1672,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/invite_user_to_channel_test.ts",
    "chars": 3295,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/mod.ts",
    "chars": 1781,
    "preview": "/** This file was autogenerated on Fri Sep 06 2024. Follow the steps in src/schema/slack/functions/_scripts/README.md to"
  },
  {
    "path": "src/schema/slack/functions/open_form.ts",
    "chars": 2376,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/open_form_test.ts",
    "chars": 4133,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/remove_reaction.ts",
    "chars": 883,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/remove_reaction_test.ts",
    "chars": 1943,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/remove_user_from_usergroup.ts",
    "chars": 1128,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/remove_user_from_usergroup_test.ts",
    "chars": 2830,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/reply_in_thread.ts",
    "chars": 2935,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/reply_in_thread_test.ts",
    "chars": 4805,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/send_dm.ts",
    "chars": 2444,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/send_dm_test.ts",
    "chars": 4178,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/send_ephemeral_message.ts",
    "chars": 1497,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/send_ephemeral_message_test.ts",
    "chars": 3221,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/send_message.ts",
    "chars": 2913,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/send_message_test.ts",
    "chars": 4773,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/share_canvas.ts",
    "chars": 1474,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/share_canvas_in_thread.ts",
    "chars": 1669,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/share_canvas_in_thread_test.ts",
    "chars": 3474,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/share_canvas_test.ts",
    "chars": 2997,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/update_channel_topic.ts",
    "chars": 1075,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/functions/update_channel_topic_test.ts",
    "chars": 2660,
    "preview": "/** This file was autogenerated. Follow the steps in src/schema/slack/functions/_scripts/README.md to rebuild **/\nimport"
  },
  {
    "path": "src/schema/slack/mod.ts",
    "chars": 207,
    "preview": "import SlackTypes from \"./schema_types.ts\";\nimport SlackFunctions from \"./functions/mod.ts\";\n\nconst SlackSchema = {\n  ty"
  },
  {
    "path": "src/schema/slack/schema_types.ts",
    "chars": 177,
    "preview": "import { SlackPrimitiveTypes } from \"./types/mod.ts\";\nimport { CustomSlackTypes } from \"./types/custom/mod.ts\";\n\nexport "
  },
  {
    "path": "src/schema/slack/types/custom/custom_slack_types_test.ts",
    "chars": 547,
    "preview": "import { assertEquals } from \"../../../../dev_deps.ts\";\nimport { CustomSlackTypes, InternalSlackTypes } from \"./mod.ts\";"
  },
  {
    "path": "src/schema/slack/types/custom/form_input.ts",
    "chars": 562,
    "preview": "import SchemaTypes from \"../../../schema_types.ts\";\nimport { DefineType } from \"../../../../types/mod.ts\";\n\nconst FormIn"
  },
  {
    "path": "src/schema/slack/types/custom/interactivity.ts",
    "chars": 548,
    "preview": "import SchemaTypes from \"../../../schema_types.ts\";\nimport { DefineType } from \"../../../../types/mod.ts\";\nimport { User"
  },
  {
    "path": "src/schema/slack/types/custom/message_context.ts",
    "chars": 560,
    "preview": "import SchemaTypes from \"../../../schema_types.ts\";\nimport { SlackPrimitiveTypes } from \"../../types/mod.ts\";\nimport { D"
  },
  {
    "path": "src/schema/slack/types/custom/mod.ts",
    "chars": 431,
    "preview": "import { InteractivityType } from \"./interactivity.ts\";\nimport { UserContextType } from \"./user_context.ts\";\nimport { Fo"
  },
  {
    "path": "src/schema/slack/types/custom/user_context.ts",
    "chars": 463,
    "preview": "import SchemaTypes from \"../../../schema_types.ts\";\nimport { SlackPrimitiveTypes } from \"../../types/mod.ts\";\nimport { D"
  },
  {
    "path": "src/schema/slack/types/mod.ts",
    "chars": 904,
    "preview": "const SlackPrimitiveTypes = {\n  blocks: \"slack#/types/blocks\",\n  canvas_id: \"slack#/types/canvas_id\",\n  canvas_template_"
  },
  {
    "path": "src/schema/types.ts",
    "chars": 204,
    "preview": "type BaseSchemaType = {\n  types?: {\n    [key: string]: string;\n  };\n};\n\n// Allow for sub-schema, i.e. schema.slack.types"
  },
  {
    "path": "src/test_utils.ts",
    "chars": 960,
    "preview": "import { assertEquals } from \"./dev_deps.ts\";\n\n// deno-lint-ignore ban-types\ntype IsAny<T> = unknown extends T ? T exten"
  },
  {
    "path": "src/test_utils_test.ts",
    "chars": 2947,
    "preview": "import { assertExists } from \"https://deno.land/std@0.152.0/testing/asserts.ts\";\nimport { assert, fail } from \"./dev_dep"
  },
  {
    "path": "src/type_utils.ts",
    "chars": 766,
    "preview": "/** @description Defines accepted recursion depth values */\nexport type RecursionDepthLevel = 0 | 1 | 2 | 3 | 4 | 5;\n\n/*"
  },
  {
    "path": "src/types/mod.ts",
    "chars": 2474,
    "preview": "import type { SlackManifest } from \"../manifest/mod.ts\";\nimport type { ManifestCustomTypeSchema } from \"../manifest/mani"
  },
  {
    "path": "src/types/types.ts",
    "chars": 908,
    "preview": "import type {\n  ParameterDefinitionWithGenerics,\n  TypedObjectProperties,\n  TypedObjectRequiredProperties,\n} from \"../pa"
  },
  {
    "path": "src/types/types_test.ts",
    "chars": 943,
    "preview": "import { DefineType } from \"./mod.ts\";\nimport { assertEquals } from \"../dev_deps.ts\";\n\nDeno.test(\"DefineType test agains"
  },
  {
    "path": "src/types.ts",
    "chars": 752,
    "preview": "// ----------------------------------------------------------------------------\n// Invocation\n// -----------------------"
  },
  {
    "path": "src/workflows/mod.ts",
    "chars": 6012,
    "preview": "import type { SlackManifest } from \"../manifest/mod.ts\";\nimport type { ManifestWorkflowSchema } from \"../manifest/manife"
  },
  {
    "path": "src/workflows/types.ts",
    "chars": 2730,
    "preview": "import type { SlackManifest } from \"../manifest/mod.ts\";\nimport type { ManifestWorkflowSchema } from \"../manifest/manife"
  },
  {
    "path": "src/workflows/workflow-step.ts",
    "chars": 4396,
    "preview": "import type { SlackManifest } from \"../manifest/mod.ts\";\nimport type {\n  ManifestFunction,\n  ManifestWorkflowStepSchema,"
  },
  {
    "path": "tests/integration/functions/runtime_context/array_parameters_test.ts",
    "chars": 14942,
    "preview": "import { assert, assertEquals, type IsAny } from \"../../../../src/dev_deps.ts\";\nimport type {\n  CanBeUndefined,\n  Cannot"
  },
  {
    "path": "tests/integration/functions/runtime_context/custom_type_parameters_test.ts",
    "chars": 10932,
    "preview": "import { assert, type IsAny, type IsExact } from \"../../../../src/dev_deps.ts\";\nimport type {\n  CanBe,\n  CanBeUndefined,"
  },
  {
    "path": "tests/integration/functions/runtime_context/empty_undefined_parameters_test.ts",
    "chars": 2022,
    "preview": "import { DefineFunction } from \"../../../../src/mod.ts\";\nimport type {\n  EnrichedSlackFunctionHandler,\n} from \"../../../"
  },
  {
    "path": "tests/integration/functions/runtime_context/incomplete_error_status_test.ts",
    "chars": 1665,
    "preview": "import { DefineFunction } from \"../../../../src/mod.ts\";\nimport type {\n  EnrichedSlackFunctionHandler,\n} from \"../../../"
  },
  {
    "path": "tests/integration/functions/runtime_context/input_parameter_optionality_test.ts",
    "chars": 3673,
    "preview": "import { assert } from \"../../../../src/dev_deps.ts\";\nimport type { CanBe, CanBeUndefined } from \"../../../../src/test_u"
  },
  {
    "path": "tests/integration/functions/runtime_context/input_parameters_test.ts",
    "chars": 3554,
    "preview": "import { assert, type IsExact } from \"../../../../src/dev_deps.ts\";\nimport { DefineFunction, Schema } from \"../../../../"
  },
  {
    "path": "tests/integration/functions/runtime_context/output_parameter_optionality_test.ts",
    "chars": 3890,
    "preview": "import { assert } from \"../../../../src/dev_deps.ts\";\nimport type { CanBe, CanBeUndefined } from \"../../../../src/test_u"
  },
  {
    "path": "tests/integration/functions/runtime_context/output_parameters_test.ts",
    "chars": 4181,
    "preview": "import { DefineFunction, Schema } from \"../../../../src/mod.ts\";\nimport type {\n  EnrichedSlackFunctionHandler,\n} from \"."
  },
  {
    "path": "tests/integration/functions/runtime_context/typed_object_property_test.ts",
    "chars": 12872,
    "preview": "import {\n  assert,\n  assertEquals,\n  assertExists,\n  type IsAny,\n  type IsExact,\n} from \"../../../../src/dev_deps.ts\";\ni"
  },
  {
    "path": "tests/integration/functions/runtime_context/untyped_object_property_test.ts",
    "chars": 1693,
    "preview": "import { assert, assertExists, type IsAny } from \"../../../../src/dev_deps.ts\";\nimport { DefineFunction, Schema } from \""
  },
  {
    "path": "tests/integration/parameters/parameter_variable_test.ts",
    "chars": 16126,
    "preview": "import { DefineProperty } from \"../../../src/parameters/define_property.ts\";\nimport { ParameterVariable } from \"../../.."
  },
  {
    "path": "tests/integration/parameters/parameter_variable_unwrapped_test.ts",
    "chars": 6429,
    "preview": "import { DefineType } from \"../../../src/types/mod.ts\";\nimport SchemaTypes from \"../../../src/schema/schema_types.ts\";\ni"
  },
  {
    "path": "tests/integration/schema/slack/functions/_scripts/write_function_files_test.ts",
    "chars": 1841,
    "preview": "import {\n  assertEquals,\n  assertExists,\n  mock,\n} from \"../../../../../../src/dev_deps.ts\";\nimport { _internals } from "
  },
  {
    "path": "tests/integration/workflows/workflows_test.ts",
    "chars": 6140,
    "preview": "import { assertEquals } from \"../../../src/dev_deps.ts\";\nimport { DefineWorkflow } from \"../../../src/workflows/mod.ts\";"
  }
]

About this extraction

This page contains the full source code of the slackapi/deno-slack-sdk GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 194 files (626.6 KB), approximately 149.8k tokens, and a symbol index with 363 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!