Integrations
Integrations
An integration is a definition: one driver with its typed configuration and credential, the feeds that bring rows into collections and the outbound rules that send or submit records. It lives in src/integration/<name>/+integration.ts as integration('<name>', { … }); the folder is its name. One external account — a WhatsApp number, a mailbox, a bot, a database, a calendar account — is an instance: a sys_integration row an administrator pairs in Settings → Integrations. Credentials are sealed on the host and never enter guest code.
Declaring an integration
// src/integration/kingdee/+integration.ts (templates/trade-operations, excerpt)
import { integration } from '@norbital-ai/bolt';
const kingdee = integration('kingdee', {
description: 'Accounting SQL Server CDC and approved order submissions.',
driver: 'sqlserver',
credential: { connectionString: { kind: 'text', label: 'SQL Server connection string' } },
configuration: { maxLagMinutes: { kind: 'int', min: 1, default: 15 }, … },
feeds: {
products: { into: 'products', key: ['external_code'], cron: '*/15 * * * *', removed: { active: false } },
…
},
outbound: {
orders: { from: 'orders', on: ['create', 'update'], when: { status: { eq: 'confirmed' } }, via: 'ledger', version: 'seal_hash' }
}
});
export default kingdee;
// one page per run: { changes, cursor, more }
kingdee.feed('products', {
read: function* (cursor, ctx) {
return yield* readCdc(projections.products, 'external_code', cursor, ctx, …);
}
});
// the committed order's payload (null skips it), then the command under its idempotency key
kingdee.outbound('orders', {
build: (row) => (row.status !== 'confirmed' || !row.seal_hash ? null : { order_id: String(row.id), … }),
run: function* (payload, ctx) {
yield* verifyOrder(payload, ctx);
return { id: yield* submitOrder(payload, ctx.key, ctx) };
}
}); driver is one of http, sqlserver, calendar, whatsapp, email, telegram, slack, discord and wechat. configuration holds safe, runtime-editable input fields; credential (http and sqlserver only) holds the secret fields an administrator enters when pairing. An http definition names its baseUrl and its auth — a credential field as bearer, a header or basic — or a definition-level oauth client named by secrets. Bodies attach after the literal by name: i.feed('<feed>', …), i.outbound('<rule>', …) and an optional i.test(…). The conversation drivers have built-in definitions of the same name; declare your own on one only to attach feeds or outbound rules.
Feeds
A feed brings rows into one collection, keyed by its remote key fields and the instance, which the engine stamps. A read feed runs on its cron and attaches read(cursor, ctx), which returns one page: its changes, the next cursor and more. A driver feed maps what the instance’s link pushes (calendar events, one cursor per calendar), and a messages feed maps the instance’s stored messages. A page’s rows, markers and cursor commit in one statement, and an unchanged page bumps no revision. removed says what a remote delete does: delete the row (the default) or patch it. A feed target exposes no create, delete or actions; the columns of its update input are its local fields, which your side writes and the feed never does.
Outbound
An outbound rule watches a collection (from) for creates or updates that match when, and queues one delivery per record in the record’s own commit, once any approval made it effective. via names the connection, or the record’s field that holds its instance; version sends again when that field changes. At delivery, build(row) returns the payload from the committed row, or null to skip it. On a conversation driver the payload is a message, and events patch the record from its delivery reports (sent, delivered, opened, bounced, failed, replied); on http or sqlserver, run(payload, ctx) performs the command under ctx.key, an immutable idempotency key. Reads of the collection carry each rule’s status in $outbound.
Instances and connections
// src/+workspace.ts (templates/on-demand-services, excerpt): the instances source addresses, by slot
connections: {
customer_mail: { integration: 'customer_mail_delivery' },
whatsapp: { integration: 'whatsapp' },
google_routes: { integration: 'google_routes' }
}
// src/integration/google_routes/+integration.ts: the API key is sealed when an administrator pairs the instance
export default integration('google_routes', {
description: 'Google Routes for drive times and traffic.',
driver: 'http',
credential: { apiKey: { kind: 'text', label: 'Google Routes API key' } },
http: {
baseUrl: 'https://routes.googleapis.com/',
auth: { header: { name: 'X-Goog-Api-Key', value: 'apiKey' } }
}
});
// in an automation's generator run (excerpt): absence is allowed, other failures propagate
import { Effect } from 'effect';
const answer = yield* ctx.request('google_routes', {
method: 'POST',
path: 'distanceMatrix/v2:computeRouteMatrix',
body: { origins, destinations, travelMode: 'DRIVE' },
output: { kind: 'list', of: { kind: 'object', fields: { duration: { kind: 'text', optional: true } } } }
}).pipe(Effect.catchTag('Unavailable', () => Effect.succeed(null)));
yield* ctx.send('whatsapp', { to: helper.phone, text: 'A warning has been filed on your record. …' }); An instance is a sys_integration row: its definition, a name, active, its configuration and, on a conversation instance, the envoy it deploys. connections in +workspace.ts name the instances the workspace’s source addresses — connections: { mail: { integration: 'email' } } makes an instance whose id is the slot; other instances are created at run time. A record refers to one through a relationship to sys_integration pinned by where: { integration: { eq: '<definition>' } }. An automation sends a message with ctx.send(<connection or instance>, …) and calls an http or sqlserver instance with ctx.request(<connection or instance>, operation), the credential resolved by the host. ctx.request fails with typed Unavailable while an instance is unpaired; recover that failure with Effect.catchTag.
Runs
A feed runs as <definition>.<feed> over every active instance, in the live environment only; a failing instance does not hold up the others, and its health lands on the instance’s status. Collection toolbars start the same run. A message delivery is retried a bounded number of times; a command is retried until it is acknowledged. Instances are managed under ordinary grants on sys_integration.