messages (inbound stream) + send (outbound dispatcher). Together
they handle 99% of what any platform integration needs to do — every
higher-level affordance (message.reply, message.react, space.edit,
space.startTyping, etc.) is sugar that routes through send.
Everything beyond those two is optional: getMessage is a known capability
that lives inside actions?; platform-specific event streams live inside
events? and surface as flat properties on the platform instance.
Minimum viable platform integration:
name, config, lifecycle, user, space, messages, send.
Properties
Partial<PlatformWiseActions<_ResolvedSpace, _MessageType, NoInferClient<_Client…
Provider-defined methods exposed on the platform instance.Two tiers share this slot:
-
Platform-wise actions (
getMessage,getMembers,getAvatar) — framework-recognized names declared inPlatformWiseActions. Override by declaring the key here with the matching signature. The framework injectsctx = { client, config, store }as the first arg and surfaces the method on the platform instance (im.getMessage(space, id)). If omitted, the framework wires a default that throwsUnsupportedError. Powers universal sugar likespace.getMessage(id). -
Platform-specific actions — free-form keys like
getAttachment. Each getsctx = { client, config, store }as the first arg; the public signature onPlatformInstance<Def>dropsctxand preserves the declared return type.
user, space,
messages, plus any declared events key) are skipped at runtime with
a warning and excluded at the type level._ConfigSchema
required
_Events
Optional escape hatch: platform-specific event streams beyond the core
messages stream (e.g. presence updates, read receipts). Each producer
is surfaced as a flat property on both spectrum and the platform
instance (e.g. spectrum.presence, slack.readReceipt).The key messages is reserved — the core inbound stream lives at the
top level, not inside events?.99% of integrations don’t need this — messages + send is the
universal contract.{ createClient: (ctx: CreateClientContext<_ConfigSchema>) => Promise<_Client>; …
required
Type:
Store{ actions: _MessageActions; schema: _MessageSchema }
EventProducer<_MessageType, _Client, output<_ConfigSchema>>
required
Type:
EventProducerInbound message stream.- Default mode: returns an
AsyncIterable<ProviderMessageRecord>— Spectrum wraps each emitted record into a fully-builtMessageand fans it out viaspectrum.messages. - Fusor mode: when
lifecycle.createClientreturns aFusorClient(constructed viafusor(platform, verify)), the signature switches to a per-payload callback(ctx: { payload, respond }) => …whose return value is the message(s) to emit and whose optionalrespondcall customises the HTTP reply sent back to fusor.
send). 99% of
integrations only need to implement messages + send._Name
required
(_: { client: NoInferClient<_Client>; config: output<_ConfigSchema>; content: {…
required
Type:
MessageUserMessageUserSpaceMessageUserSpaceMessageUserSpaceMessageUserMessageUserSpaceMessageUserSpaceMessageUserMessageUserMessageUserStoreProviderMessageRecordSend a piece of Content to a space. The provider inspects
content.type and dispatches accordingly — text, attachments, reactions,
replies, edits, typing indicators, and any other content type all flow
through this single action.Returns a ProviderMessageRecord (id + timestamp) for content that
produces a message — including reactions, whose record is the unsend
handle (synthesize a deterministic id when the platform assigns none);
returns undefined only for fire-and-forget control signals (typing,
edits, renames, avatars, unsends) on platforms that don’t return ids.One of the two universal platform contracts (along with messages).{ actions: _SpaceActions; create: (_: { client: NoInferClient<_Client>; config:…
required