Skip to content

feat(slack): add the Slack Web API SDK - #551

Merged
Mkassabov merged 8 commits into
feat/google-workspace-sdkfrom
feat/slack-sdk
Sep 7, 2026
Merged

Mkassabov merged 8 commits into
feat/google-workspace-sdkfrom
feat/slack-sdk

Conversation

@Mkassabov

Copy link
Copy Markdown
Collaborator

Summary

Adds @distilled.cloud/slack — a full Slack Web API SDK (317 operations across 34 service modules), generated from the JSON twins of the docs.slack.dev method reference.

Slack has no OpenAPI document (slack-api-specs froze in 2020), but every docs.slack.dev reference page serves a structured JSON twin by appending .json to the URL. The pipeline mirrors cloudflare's committed-docs flow:

  • scripts/download-docs.ts — crawls the method index + 317 method JSON twins (plus scopes/types/objects lists) into specs/ (committed). Example blocks are stripped at download time: they embed realistic-looking third-party credentials that trip push protection, and the converter never reads them.
  • scripts/convert.ts — method pages → per-family OpenAPI slices → 34 Smithy models via core's shared convertOpenApiToSmithy. Named refs ({"schema": "channel"}) have no JSON twins upstream, so ID-like names (mined from the corpus — channel is always the ID string) inline as string; object refs inline as loose Documents, enrichable later via patches.
  • scripts/generate.ts — the provider spec for the shared smithy→SDK compiler. Wire-verbatim snake_case args; per-method error slugs and required scopes in every operation's doc comment.

Provider shape

  • ok/error envelope at HTTP 200: SlackProtocol dispatches the error slug — auth slugs → Unauthorized, rate_limitedSlackRateLimited (honoring Retry-After), server hiccups → retryable 5xx classes, everything else → SlackError carrying the slug.
  • Form-urlencoded is the default input encoding — JSON only where the docs declare json_input_supported: true. Verified live that Slack ignores JSON bodies without an Authorization header, which is exactly what the tokenless OAuth exchange methods send. The protocol serializes Slack-style: ID arrays comma-joined, rich values JSON-encoded (core's bracket notation is not Slack's dialect).
  • Tokenless OAuth exchange (oauth.v2.access, openid.connect.token, …) works via an empty-token credential — the Authorization header is omitted.
  • admin.analytics.getFile's gzipped-file answer returns as raw bytes.

Verification

Live against slack.com/api: api.test round-trip; form, query, and JSON encodings all parse server-side; error mapping (not_authed/invalid_authUnauthorized, invalid_codeSlackError; tokenless oauth.v2.access proves the form path transmits args). Both tsconfigs typecheck clean; package is wired into the root build and generate-all.

Known gaps (follow-ups)

  • Output object types are loose (unknown) where Slack's docs don't structure them (message, conversation, user, …) — patch territory.
  • No cursor auto-pagination profile yet (response_metadata.next_cursor stays a plain response member).

Supersedes #453 (same commits, on an origin branch) rebased onto the convert-time patch / verbNoun pipeline from #545/#548/#549. Operation names are now verbNoun (postMessage, archiveConversation, listUsers, uploadFile).

@alchemy-version-bot

Copy link
Copy Markdown
Contributor

Install the packages built from this commit:

Distilled

@distilled.cloud/core

bun add https://pkg.ing/@distilled.cloud/core/7fb1fa9

@distilled.cloud/aws

bun add https://pkg.ing/@distilled.cloud/aws/7fb1fa9

@distilled.cloud/axiom

bun add https://pkg.ing/@distilled.cloud/axiom/7fb1fa9

@distilled.cloud/azure

bun add https://pkg.ing/@distilled.cloud/azure/7fb1fa9

@distilled.cloud/cloudflare

bun add https://pkg.ing/@distilled.cloud/cloudflare/7fb1fa9

@distilled.cloud/coinbase

bun add https://pkg.ing/@distilled.cloud/coinbase/7fb1fa9

@distilled.cloud/discord

bun add https://pkg.ing/@distilled.cloud/discord/7fb1fa9

@distilled.cloud/expo-eas

bun add https://pkg.ing/@distilled.cloud/expo-eas/7fb1fa9

@distilled.cloud/fly-io

bun add https://pkg.ing/@distilled.cloud/fly-io/7fb1fa9

@distilled.cloud/forgejo

bun add https://pkg.ing/@distilled.cloud/forgejo/7fb1fa9

@distilled.cloud/gcp

bun add https://pkg.ing/@distilled.cloud/gcp/7fb1fa9

@distilled.cloud/github

bun add https://pkg.ing/@distilled.cloud/github/7fb1fa9

@distilled.cloud/hetzner

bun add https://pkg.ing/@distilled.cloud/hetzner/7fb1fa9

@distilled.cloud/kubernetes

bun add https://pkg.ing/@distilled.cloud/kubernetes/7fb1fa9

@distilled.cloud/mongodb-atlas

bun add https://pkg.ing/@distilled.cloud/mongodb-atlas/7fb1fa9

@distilled.cloud/neon

bun add https://pkg.ing/@distilled.cloud/neon/7fb1fa9

@distilled.cloud/planetscale

bun add https://pkg.ing/@distilled.cloud/planetscale/7fb1fa9

@distilled.cloud/posthog

bun add https://pkg.ing/@distilled.cloud/posthog/7fb1fa9

@distilled.cloud/prisma-postgres

bun add https://pkg.ing/@distilled.cloud/prisma-postgres/7fb1fa9

@distilled.cloud/railway

bun add https://pkg.ing/@distilled.cloud/railway/7fb1fa9

@distilled.cloud/stripe

bun add https://pkg.ing/@distilled.cloud/stripe/7fb1fa9

@distilled.cloud/supabase

bun add https://pkg.ing/@distilled.cloud/supabase/7fb1fa9

@distilled.cloud/turso

bun add https://pkg.ing/@distilled.cloud/turso/7fb1fa9

@distilled.cloud/typesense

bun add https://pkg.ing/@distilled.cloud/typesense/7fb1fa9

@distilled.cloud/vercel

bun add https://pkg.ing/@distilled.cloud/vercel/7fb1fa9

@distilled.cloud/workos

bun add https://pkg.ing/@distilled.cloud/workos/7fb1fa9

@Mkassabov
Mkassabov changed the base branch from main to feat/forgejo-sdk September 7, 2026 12:21
@Mkassabov
Mkassabov added this pull request to the merge queue Sep 7, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a manual request Sep 7, 2026
wyattjoh and others added 8 commits September 7, 2026 08:36
Add @distilled.cloud/forgejo: one service module per API tag, compiled
from the Swagger 2.0 template Forgejo ships at a release tag. The
spec-mirror-forgejo fetch script (stacks/distilled-submodules/spec-repos/
forgejo) pins the tag and fills in the two server-side placeholders the
way a root-mounted instance renders them. Credentials carry the instance
URL alongside the token, since a self-hosted forge has no default API
root; the protocol sends Forgejo's `token` authorization scheme.

Patches type what the document leaves out: the 500 an organization
delete answers while it still owns repositories, the 403s the list
endpoints answer for a non-member, the 409 an organization repository
create answers for a taken name, the path/body `username` clash on
adminCreateOrg, and the always-present members of the response
definitions the Go structs never omit.
Smithy-pointer patches now apply in finalizeConvert (the pipeline #545
introduced) instead of a per-slug loop in convert; the one such patch
targets the operation by its verbNoun name (DeleteOrg).
Slack has no OpenAPI document (slack-api-specs froze in 2020), but every
docs.slack.dev reference page serves a structured JSON twin by appending
.json to the URL. The pipeline mirrors cloudflare's committed-docs flow:

  scripts/download-docs.ts  crawl the method index + 317 method JSON twins
                            (plus scopes/types/objects lists) into specs/
  scripts/convert.ts        method pages -> per-family OpenAPI slices ->
                            34 Smithy models via core's shared converter
  scripts/generate.ts       Smithy models -> src/services (317 operations)

Provider shape:
  - ok/error envelope at HTTP 200: SlackProtocol dispatches the error slug
    (auth slugs -> Unauthorized, rate_limited -> SlackRateLimited with
    Retry-After, server hiccups -> retryable 5xx classes, everything else
    -> SlackError carrying the slug; per-method slugs are listed in each
    operation's doc comment)
  - form-urlencoded is the default input encoding (JSON only where the
    docs declare json_input_supported - verified live that JSON bodies
    are ignored without an Authorization header); the protocol serializes
    Slack-style: ID arrays comma-joined, rich values JSON-encoded
  - tokenless OAuth exchange methods work via an empty-token credential
    (the Authorization header is omitted)
  - admin.analytics.getFile's gzipped file answer returns as raw bytes

Verified live against slack.com/api: api.test round-trip, form + query +
JSON encodings, and the error mapping (not_authed/invalid_auth ->
Unauthorized, invalid_code -> SlackError).
…eric code

Core's ErrorMatcher codes are numeric; Slack's are string slugs. The slug
already rides in the matcher's message argument, so patch-declared per-op
classes match on status/message. Fixes the strict (--noCheck false) CI
typecheck.
Slack's cursor convention (pass cursor, follow
response_metadata.next_cursor, empty string = done) maps directly onto
core's paginateCursor traversal:

  - convert.ts stamps smithy.api#paginated on every method that takes a
    cursor arg and has an identifiable items list (the sole top-level
    array member of its documented output, plus explicit overrides for
    conversations.history/replies whose outputs carry several arrays) —
    42 operations across 11 families
  - the typed response_metadata.next_cursor struct is modeled uniformly
    on every cursor op's output (the docs model it on only ~2/3, some as
    an opaque ref), so the traversal has a cursor to follow and callers
    can read it
  - src/pagination.ts supplies the slackPaginate strategy; the plain
    SlackProtocol already keeps response_metadata (a modeled member), so
    no separate paginated protocol is needed

Cursor methods whose docs model no items list (admin.emoji.list's map,
admin.conversations.getTeams' missing member) stay plain operations
rather than guessing — patch territory.
Rebased on main after #545/#548/#549. convert ends in finalizeConvert.
Operation names are verbNoun: a camelCase Slack action is used as-is
(chat.postMessage → postMessage); a bare action is handed to the
converter as Object_action so its policy orders it
(admin.apps.permissions.add → addAppsPermissions, conversations.archive
→ archiveConversation). package.json uses the named pnpm catalogs, and
slack is registered as a blocked spec repo (the docs JSON twins are
committed in-repo).
@Mkassabov
Mkassabov added this pull request to the merge queue Sep 7, 2026
Merged via the queue into main with commit 3082a31 Sep 7, 2026
4 checks passed
Base automatically changed from feat/forgejo-sdk to feat/google-workspace-sdk September 7, 2026 12:42
@Mkassabov
Mkassabov deleted the feat/slack-sdk branch September 7, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants