SDK reference
The public API of @beetlio/connect 0.6: exports, the integration definition, the sync context, auth helpers and CLI commands.
@beetlio/connect is the TypeScript SDK you write Connect packages with. The SDK is open source
under the Apache-2.0 license: the full source and type definitions are on
GitHub, and its user guide is at
beetlio.github.io/connect. This page covers version 0.6,
which needs Node.js 24.2 or newer.
npm install --save-dev @beetlio/connectEntry points
| Import | Use |
|---|---|
@beetlio/connect | Everything you need to write a package: defineIntegration, z, auth, the helpers and the types. |
@beetlio/connect/builder | packIntegration, buildIntegration and createIntegrationManifest, for your own build tooling. |
@beetlio/connect/host | For programs that execute integrations. You do not need it to write a package for Beetl. |
Exports
| Export | Kind | Purpose |
|---|---|---|
defineIntegration(definition) | Function | Defines the integration. Make it the default export of integration.ts. |
z | Namespace | Zod 4, re-exported. Use it for every schema so versions match. |
auth | Object | Authentication helpers. See Auth helpers. |
secret(schema) | Function | Marks a string credential as secret. Its value is masked in prompts and forms. |
batchRecords(source) | Async generator | Groups a record-only stream into batches. Use with yield*. |
storageRecord(schema) | Function | Turns a provider object schema into a record schema with fixed columns. |
HttpError | Class | Thrown by ctx.json for HTTP failures. Has status, detail and requestId. |
Types such as SyncContext, Batch and RetryPolicy are exported too.
The SDK can also define destinations, which write records to an API. Beetl runs a package's
syncs, and a package installed in Beetl must declare at least one sync.
The integration definition
import { auth, defineIntegration, secret, z } from "@beetlio/connect";
const Order = z.object({ id: z.string(), total: z.number(), status: z.string() });
const Page = z.object({ orders: z.array(Order), next: z.string().nullable() });
export default defineIntegration({
key: "acme",
displayName: "Acme",
connection: {
origin: { type: "input", input: "baseUrl" },
inputs: z.strictObject({ baseUrl: z.url().meta({ title: "API URL" }) }),
auth: auth.custom({
credentials: z.strictObject({ apiKey: secret(z.string().min(1)) }),
headers: { "x-api-key": { credential: "apiKey" } },
}),
async verify(ctx) {
await ctx.json("/me", z.object({ id: z.string() }));
},
},
syncs: (sync) => ({
orders: sync({
records: Order,
inputs: z.strictObject({ status: z.enum(["open", "closed"]).default("open") }),
async *run(ctx) {
for await (const page of ctx.paginate({
request: {
path: "/orders",
query: { status: ctx.config.sync.status },
},
schema: Page,
next: ({ data, request }) =>
data.next === null
? undefined
: { ...request, query: { ...request.query, cursor: data.next } },
})) {
yield { records: page.data.orders };
}
},
}),
}),
});This example reads every order with the chosen status on each run. Its pagination cursor is used only within that run, so it suits the Replace write mode, which the person who adds the sync chooses. For incremental reads with a checkpoint that carries across runs, see Authoring a sync.
defineIntegration
| Field | Type | Notes |
|---|---|---|
key | string | Identifies the integration. |
displayName | string | Shown in the app. |
description | string, optional | Shown in the app. |
icon | "icon.png" or "icon.webp", optional | A file in the package. List it in package.json files. |
connection | ConnectionDefinition | See below. |
syncs | (sync) => Record<string, SyncDefinition> | Build each sync with the sync function passed in, so types are inferred. Keys are sync keys. |
connection
| Field | Type | Notes |
|---|---|---|
origin | ProviderOriginDefinition | A fixed URL, { type: "input", input } (a connection setting), { type: "environment", input, values } (a setting mapped to fixed URLs) or { type: "oauth", oauthTokenField } (an OAuth token-response field). Authenticated origins require HTTPS. |
auth | AuthDefinition, optional | Built with an auth helper. Omit it for public APIs. |
inputs | z.ZodObject, optional | Connection settings, shown as a form. Use z.strictObject. |
retry | RetryPolicy or false, optional | maxAttempts, statuses, methods, initialDelayMs, maxDelayMs. The default retries status 408, 429, 500, 502, 503 and 504 on GET, HEAD and OPTIONS, up to 3 attempts. false disables retries. |
verify(ctx) | (ctx: ConnectionContext) => Promise<void>, optional | Make a small authenticated request and throw if access is invalid. ctx.config holds the connection settings. |
Input schemas become forms, so they cannot use transforms or custom refinements. Set labels and
help text with .meta({ title, description }).
sync(definition)
| Field | Type | Notes |
|---|---|---|
records | z.ZodObject | The schema of each output row. Its output must be representable as JSON Schema. |
inputs | z.ZodObject, optional | Sync settings, shown when a user adds the sync. |
checkpoint | Zod schema, optional | The shape of the position you resume from. Required before you yield a checkpoint. A sync that declares one resumes from it when its write mode is Append. |
displayName | string, optional | Shown in the app. |
run(ctx) | Async generator | Yields batches. Returning ends the run successfully. |
Batches
Each value run yields is one batch. Beetl accepts a batch's records and checkpoint together
before the generator continues; the dataset changes only when the run succeeds.
A sync does not choose a write mode, declare a primary key or report deletions. The person who adds the sync chooses Append or Replace. See Write modes.
| Field | Type | Notes |
|---|---|---|
records | Array | Rows, each parsed with the records schema. |
checkpoint | Checkpoint type, optional | The position to resume after this batch. Omitting it keeps the previous one; null is a value. |
A batch holds at most 10,000 records. For the per-run limits, see Write modes.
Sync context
run(ctx) receives a SyncContext.
| Member | Type | Notes |
|---|---|---|
ctx.config.connection | Object | Parsed connection settings. |
ctx.config.sync | Object | Parsed sync settings, with defaults applied. |
ctx.checkpoint | Checkpoint type or undefined | The last committed checkpoint. undefined on the first run, after the sync's settings change, and in every Replace run. |
ctx.signal | AbortSignal | Pass it to async work that does not go through ctx. |
ctx.log | IntegrationLogger | debug, info, warn and error, each (message, fields?) => Promise<void>. Lines appear on the connection's Logs tab. Never log credentials. |
ctx.fetch(path, init?) | Promise<Response> | Sends an authenticated request to a path relative to the origin, or a same-origin URL. init takes method, headers, body and signal. Redirects are not followed. |
ctx.json(path, schema, init?) | Promise<output> | Like fetch, then parses the JSON body with schema. Throws HttpError for an unsuccessful status. |
ctx.paginate(options) | Async generator of PaginationPage | See below. |
ctx.paginate takes a request (path, optional query and headers), a schema for each page,
and a next function. next receives the page and returns the next request, or undefined to
stop. Each page has data, response (status and headers), request and index. A
pagination stops with an error after 10,000 pages or on a repeated request.
Await every ctx call. The SDK adds credentials to each request, so sync code never handles
secrets.
Auth helpers
| Helper | Credential fields the user enters | Use |
|---|---|---|
auth.bearer() | token | Bearer token in the Authorization header. |
auth.basic() | username, password | HTTP basic authentication. |
auth.apiKey({ in, name }) | apiKey | An API key in a header or query parameter called name. |
auth.oauth2({ issuer, authorizationUrl, tokenUrl, scopes, clientSecret?, tokenFields? }) | clientId, plus clientSecret when clientSecret: true | OAuth 2.0 authorization code with PKCE and token refresh. tokenFields maps token-response fields for use as an origin. |
auth.tokenExchange({ credentials, request, response, session? }) | Your credentials schema | Exchanges credentials for a session token at request.path, reads it from response.tokenPath, and renews it by response.expiry. |
auth.custom({ credentials, headers?, query? }) | Your credentials schema | Places credentials in headers or query parameters with { credential: "field" }. |
Custom and token-exchange credentials are strict objects of strings, and they cannot have defaults.
Wrap sensitive fields in secret(). See Authentication.
Helpers
batchRecords(source) takes an iterable or async iterable of records and yields record-only batches
of a suitable size. Use it as yield* batchRecords(rows) when a sync has no checkpoint; yield
batches yourself when each one carries a checkpoint.
storageRecord(schema) wraps a provider object schema so every top-level field gets a fixed column
type. Strings, numbers, booleans, arrays and strict nested objects keep their type. A union of
scalar types is stored as text, and other dynamic values, such as records with arbitrary keys, are
stored as JSON text. The schema must be a plain object without a catchall or root refinements.
import { auth, batchRecords, defineIntegration, storageRecord, z } from "@beetlio/connect";
const Customer = z.object({
id: z.number(),
name: z.string(),
attributes: z.record(z.string(), z.unknown()),
});
export default defineIntegration({
key: "acme-customers",
displayName: "Acme customers",
connection: { origin: "https://api.example.com", auth: auth.bearer() },
syncs: (sync) => ({
customers: sync({
records: storageRecord(Customer),
async *run(ctx) {
yield* batchRecords(await ctx.json("/customers", z.array(Customer)));
},
}),
}),
});CLI
The SDK installs the beetl-connect command. Run it with npx beetl-connect, and add --help to
any command for every option.
| Command | Options | What it does |
|---|---|---|
configure <dir> [sync] | --profile, --connection, --origin, --inputs <json>, --reauthorize | Prompts for settings and credentials, authorizes OAuth, runs verify, and saves a local profile. |
sync <dir> [sync] | --profile, --output <path>, --state <path>, --mode append|replace | Runs a sync and writes its records as NDJSON. Checkpoints are saved under .beetl/state/ unless you pass --state. --mode replace starts from the beginning and writes the file only when the run succeeds. |
pack <dir> | --output <path> | Creates the .tgz you upload to Beetl and prints the files it contains. |
compatibility | None | Checks the installed SDK against its compatibility fixtures. |
The sync argument is optional when the integration has one sync. Local credentials are saved unencrypted in files only your user can read, in the operating system's user configuration directory. See Local development and Pack and upload.
Last updated on