Skip to content

ChannelTransport

The channel contract: what an integration that gives channels declares, as the harness and sandbox contracts declare theirs. A channel transport receives the events an external system sends, each placing a message in an external conversation, such as a Slack thread or an email thread, and posts messages as replies in such a conversation, saying what the system returned for them. It can edit or delete a posted message when the system can, and it knows its own identity there, so that what it posted is not received as new.

The transport identifies conversations, messages, and senders by the system’s own ids and keeps what else it needs of a conversation in its details; amends keeps those as they are and gives them back when it posts there.

Since v0.1.0

The system refused what it was asked for good, as for a conversation that is gone.

Signature

declare class RefusedError

Source

Since v0.1.0

The transport rejected an inbound request that is not the system’s, or whose signature does not hold, or that it cannot read; the system should be answered so.

Signature

declare class RejectedError

Source

Since v0.1.0

The error a transport fails with: why, and what the system said as its cause. Only an unavailable system is worth asking again.

Signature

declare class TransportError

Source

Since v0.1.0

Why the transport could not do what was asked: one of the errors above, which TransportError.reason holds.

Signature

declare const TransportErrorReason: Schema.toTaggedUnion<
"_tag",
readonly [typeof RejectedError, typeof RefusedError, typeof UnavailableError]
>

Source

Since v0.1.0

Signature

type TransportErrorReason = typeof TransportErrorReason.Type

Source

Since v0.1.0

The system could not be reached now, which may pass.

Signature

declare class UnavailableError

Source

Since v0.1.0

A channel’s transport: how a connection to the system receives events and posts there.

Example (A transport for a chat system that sends each message to a webhook)

import { ChannelTransport } from "@amends-dev/channel"
import { DateTime, Effect, Schema } from "effect"
// What the system's webhook sends: one message in a room, as JSON.
const Webhook = Schema.fromJsonString(
Schema.Struct({ room: Schema.String, id: Schema.String, from: Schema.String, text: Schema.String })
)
// The system's API, which answers a post with the new message's id.
declare const sendToRoom: (room: string, text: string) => Effect.Effect<string, Error>
const failWith = (reason: ChannelTransport.TransportErrorReason) => (cause: unknown) =>
new ChannelTransport.TransportError({ reason, cause })
export const transport: ChannelTransport.ChannelTransport = {
self: undefined,
// A real transport first checks the signature the system puts in `inbound.headers`.
receive: (inbound) =>
Effect.gen(function* () {
const message = yield* Schema.decodeUnknownEffect(Webhook)(inbound.body).pipe(
Effect.mapError(failWith(new ChannelTransport.RejectedError({})))
)
const event = ChannelTransport.Event.cases.MessageReceived.make({
conversation: { id: message.room, details: {} },
messageId: message.id,
sender: { id: message.from, name: message.from },
text: message.text,
receivedAt: yield* DateTime.now
})
return { events: [event] }
}),
post: (conversation, outgoing) =>
sendToRoom(conversation.id, `${outgoing.author}: ${outgoing.text}`).pipe(
Effect.map((messageId) => ({ messageId })),
Effect.mapError(failWith(new ChannelTransport.UnavailableError({})))
)
}

Signature

export interface ChannelTransport {
/**
* The transport's own identity in the system, such as a bot user's id, when it has one. What
* this identity sends is the transport's own post, received back, not a message to take in.
*/
readonly self: ExternalId | undefined
/** The events in a request the system sent, after checking that it is the system's. */
readonly receive: (inbound: Inbound) => Effect.Effect<Received, TransportError>
/** Post `outgoing` as a reply in `conversation`. */
readonly post: (conversation: Conversation, outgoing: Outgoing) => Effect.Effect<Posted, TransportError>
/** Replace the text of the message `messageId` it posted in `conversation`, when the system can. */
readonly edit?: (
conversation: Conversation,
messageId: ExternalId,
outgoing: Outgoing
) => Effect.Effect<void, TransportError>
/** Delete the message `messageId` it posted in `conversation`, when the system can. */
readonly delete?: (conversation: Conversation, messageId: ExternalId) => Effect.Effect<void, TransportError>
}

Source

Since v0.1.0

An external conversation: its id, and what the transport needs to post there besides it, such as the Slack channel a thread is in, which amends keeps and gives back as it is.

Signature

declare const Conversation: Schema.Struct<{
readonly id: Schema.NonEmptyString
readonly details: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>
}>

Source

Since v0.1.0

Signature

export interface Conversation extends Schema.Schema.Type<typeof Conversation> {}

Source

Since v0.1.0

One event the external system sent: a message received in a conversation, with the title the system gives the conversation when it has one, such as an email’s subject; a message edited; or a message deleted.

Signature

declare const Event: Schema.TaggedUnion<{
readonly MessageReceived: Schema.TaggedStruct<
"MessageReceived",
{
readonly conversation: Schema.Struct<{
readonly id: Schema.NonEmptyString
readonly details: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>
}>
readonly messageId: Schema.NonEmptyString
readonly sender: Schema.Struct<{ readonly id: Schema.NonEmptyString; readonly name: Schema.NonEmptyString }>
readonly text: Schema.String
readonly title: Schema.optionalKey<Schema.String>
readonly receivedAt: Schema.DateTimeUtcFromString
}
>
readonly MessageEdited: Schema.TaggedStruct<
"MessageEdited",
{
readonly conversation: Schema.Struct<{
readonly id: Schema.NonEmptyString
readonly details: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>
}>
readonly messageId: Schema.NonEmptyString
readonly sender: Schema.Struct<{ readonly id: Schema.NonEmptyString; readonly name: Schema.NonEmptyString }>
readonly text: Schema.String
readonly editedAt: Schema.DateTimeUtcFromString
}
>
readonly MessageDeleted: Schema.TaggedStruct<
"MessageDeleted",
{
readonly conversation: Schema.Struct<{
readonly id: Schema.NonEmptyString
readonly details: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>
}>
readonly messageId: Schema.NonEmptyString
readonly sender: Schema.Struct<{ readonly id: Schema.NonEmptyString; readonly name: Schema.NonEmptyString }>
readonly deletedAt: Schema.DateTimeUtcFromString
}
>
}>

Source

Since v0.1.0

Signature

type Event = typeof Event.Type

Source

Since v0.1.0

An identifier of the external system’s own: of a conversation, a message, or a user there.

Signature

declare const ExternalId: Schema.NonEmptyString

Source

Since v0.1.0

Signature

type ExternalId = typeof ExternalId.Type

Source

Since v0.1.0

What the external system sent a host, as the host received it: the request’s headers and body.

Signature

declare const Inbound: Schema.Struct<{
readonly headers: Schema.$Record<Schema.String, Schema.String>
readonly body: Schema.String
}>

Source

Since v0.1.0

Signature

export interface Inbound extends Schema.Schema.Type<typeof Inbound> {}

Source

Since v0.1.0

A message to post in an external conversation: its text, and what its author is called.

Signature

declare const Outgoing: Schema.Struct<{ readonly text: Schema.String; readonly author: Schema.NonEmptyString }>

Source

Since v0.1.0

Signature

export interface Outgoing extends Schema.Schema.Type<typeof Outgoing> {}

Source

Since v0.1.0

What the external system returned for a posted message: the id it gave it.

Signature

declare const Posted: Schema.Struct<{ readonly messageId: Schema.NonEmptyString }>

Source

Since v0.1.0

Signature

export interface Posted extends Schema.Schema.Type<typeof Posted> {}

Source

Since v0.1.0

What the transport made of an inbound request: the events in it, none for a request that carries no message, and what to answer the system with when it expects an answer, as Slack’s URL verification does.

Signature

declare const Received: Schema.Struct<{
readonly events: Schema.$Array<
Schema.TaggedUnion<{
readonly MessageReceived: Schema.TaggedStruct<
"MessageReceived",
{
readonly conversation: Schema.Struct<{
readonly id: Schema.NonEmptyString
readonly details: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>
}>
readonly messageId: Schema.NonEmptyString
readonly sender: Schema.Struct<{ readonly id: Schema.NonEmptyString; readonly name: Schema.NonEmptyString }>
readonly text: Schema.String
readonly title: Schema.optionalKey<Schema.String>
readonly receivedAt: Schema.DateTimeUtcFromString
}
>
readonly MessageEdited: Schema.TaggedStruct<
"MessageEdited",
{
readonly conversation: Schema.Struct<{
readonly id: Schema.NonEmptyString
readonly details: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>
}>
readonly messageId: Schema.NonEmptyString
readonly sender: Schema.Struct<{ readonly id: Schema.NonEmptyString; readonly name: Schema.NonEmptyString }>
readonly text: Schema.String
readonly editedAt: Schema.DateTimeUtcFromString
}
>
readonly MessageDeleted: Schema.TaggedStruct<
"MessageDeleted",
{
readonly conversation: Schema.Struct<{
readonly id: Schema.NonEmptyString
readonly details: Schema.$Record<Schema.String, Schema.Codec<Schema.Json, Schema.Json, never, never>>
}>
readonly messageId: Schema.NonEmptyString
readonly sender: Schema.Struct<{ readonly id: Schema.NonEmptyString; readonly name: Schema.NonEmptyString }>
readonly deletedAt: Schema.DateTimeUtcFromString
}
>
}>
>
readonly answer: Schema.optionalKey<Schema.String>
}>

Source

Since v0.1.0

Signature

export interface Received extends Schema.Schema.Type<typeof Received> {}

Source

Since v0.1.0

Who sent a message in the external system: their id there, and what they are called.

Signature

declare const Sender: Schema.Struct<{ readonly id: Schema.NonEmptyString; readonly name: Schema.NonEmptyString }>

Source

Since v0.1.0

Signature

export interface Sender extends Schema.Schema.Type<typeof Sender> {}

Source

Since v0.1.0