Connections in Headless
A headless integration declares its connections in its configuration, instead of on a config page.
Your init function, server functions, and flows can then use them, and your app lets customers choose or create them.
Declare connections
Add connections to the connections property of a configuration scope.
Each key names the connection within your integration:
import {
configuration,
customerActivatedConnection,
} from "@prismatic-io/spectral";
import { z } from "zod";
export const integrationConfiguration = configuration({
instance: {
schema: z.object({
channel: z.object({ id: z.string(), name: z.string() }),
}),
version: "slack-alerts-v1",
connections: {
slack: customerActivatedConnection({ stableKey: "slack-oauth" }),
},
},
});
The instance scope accepts these kinds of connections:
| Function | Connection type |
|---|---|
customerActivatedConnection({ stableKey }) | A customer-activated connection your customer creates. |
organizationActivatedConnection({ stableKey }) | An org-activated connection your organization supplies. |
connectionConfigVar({ ... }) | An integration-specific connection with its own inputs. |
The userLevel scope accepts userActivatedConnection({ stableKey }) for a user-level connection, or connectionConfigVar().
Use connections in init and server functions
init and server functions receive only the connections they declare.
List them with their scope, such as instance.slack, and read them from context.connections:
import { serverFunction, util } from "@prismatic-io/spectral";
export const listChannels = serverFunction({
label: "List channels",
inputSchema: z.object({ search: z.string().optional() }),
outputSchema: z.array(z.object({ id: z.string(), name: z.string() })),
connections: ["instance.slack"],
perform: async (context, { search }) => {
const token = util.types.toString(
context.connections.instance.slack.token?.access_token,
);
return searchSlackChannels(token, search);
},
});
init declares connections the same way, in init.connections.
Spectral checks the declared keys against your configuration at compile time, so a typo in a connection key is a type error.
Use connections in flows
Flows read the instance's connections from context.connections, by scope:
onExecution: async (context) => {
const slack = context.connections?.instance?.slack;
// ...
},
Let customers choose connections in your app
When your app loads a configuration with Solis, the configuration lists the connections that init and each server function need.
Each requirement offers the customer's existing connections that can fill it.
Your organization supplies org-activated connections, so they're never offered to customers.
Your app keeps the customer's choices as a list of { key, id } selections, using each requirement's key exactly as the SDK gives it, and passes them when it calls init or a server function:
// A requirement from configuration.data.connections, and the option the customer chose
const [requirement] = configuration.data.connections.data.init;
await configuration.actions.init.execute({
connections: [{ key: requirement.key, id: selectedConnectionId }],
});
A requirement backed by a customer-activated connection can also create a new connection in one click. For OAuth 2.0 connections, this opens the provider's consent screen.
See Building configuration UIs with Solis React for the details.