@ontrove/sdk
@ontrove/sdk — the thin standard library for authoring Trove connectors.
You write a sync(ctx) that fetches from a source and returns documents; the
SDK owns the document shape (which maps 1:1 onto IngestDocumentInput), the
typed ctx capability object, the watermark/cursor model, the local-run
harness the CLI drives, and manifest validation.
It is the symmetric sibling of @ontrove/mcp: a connector returns documents to
be stored (defineConnector + sync); an MCP server returns tool results to
be read live (defineMcpServer).
Example
Section titled “Example”import { defineConnector } from '@ontrove/sdk';
export default defineConnector({ async sync(ctx) { const res = await ctx.fetch('https://hn.algolia.com/api/v1/search?tags=front_page'); const { hits } = await res.json(); return hits.map((hit) => ({ id: hit.objectID, title: hit.title, text: hit.story_text ?? hit.title, url: hit.url, author: hit.author, date: new Date(hit.created_at_i * 1000).toISOString(), contentType: 'bookmark', })); },});Interfaces
Section titled “Interfaces”ConnectorContext
Section titled “ConnectorContext”The single argument to sync — a capability object: everything a connector
reaches the outside world through is on ctx. There is no ambient authority.
ctx.credentials (Keychain-resolved auth material) and ctx.browser (a
Playwright browser for needs_browser connectors) are PROPOSED, not part
of the shipped contract, and so are intentionally absent here.
Type Parameters
Section titled “Type Parameters”| Type Parameter | Default type |
|---|---|
C | Record<string, unknown> |
Properties
Section titled “Properties”| Property | Modifier | Type | Description |
|---|---|---|---|
config | readonly | C | The user’s preference values, keyed by the field names declared in manifest.json config. Preferences only — never credentials. Feed URLs, usernames, section lists, and filters live here; auth material lives in the macOS Keychain, surfaced (PROPOSED) via ctx.credentials, never here. |
cursor | readonly | Watermark | The source’s current watermark (the position from the previous run), or { type: 'none' } on the first sync. Read-only — advance the cursor by returning a new Watermark from sync, not by mutating this. |
fetch | public | FetchLike | Behaves like the standard fetch. Prefer ctx.fetch over global fetch — in the Mac app it routes through per-connector timeouts, retry, and rate-limit handling, surfacing failures in the connector’s error log. |
Methods
Section titled “Methods”log(...args: unknown[]): void;Structured log entry, surfaced in the Mac app’s connector logs and the
createSyncRun audit record. Useful for reporting counts and progress.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
…args | unknown[] |
Returns
Section titled “Returns”void
now(): Date;The current wall-clock time as a Date. Injected (rather than read from a
global) so syncs are deterministic under test and the local-run harness.
Returns
Section titled “Returns”Date
ConnectorDocument
Section titled “ConnectorDocument”A single document a connector returns from sync. The fields map 1:1 onto
the GraphQL IngestDocumentInput the Mac app pushes via ingestDocuments:
| SDK field | IngestDocumentInput field |
|---|---|
id | externalId (required) |
title | title |
text | text |
audioUrl | audioUrl |
url | url |
author | author |
date | date (ISO 8601 DateTime) |
tags | tags |
metadata | metadata (JSON) |
contentType | contentType (ContentType) |
Dedup is keyed on (source, id): returning the same id twice is safe and is
skipped on the server, so sync can be idempotent and retried freely. At least
one of text or audioUrl must be present — an audio-only document triggers
transcription (Whisper), and the transcript becomes the document text.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
audioUrl? | string | URL to an audio file. Maps to IngestDocumentInput.audioUrl. Trove downloads and transcribes it; the transcript becomes the document text and the document is indexed as contentType: transcript. Provide instead of (or alongside) text. |
author? | string | Content author. Maps to IngestDocumentInput.author. For podcasts, set this to the show name (Trove convention). |
contentType? | ConnectorContentType | One of text, transcript, highlight, bookmark. Maps to IngestDocumentInput.contentType. Defaults to text (transcribed audio becomes transcript automatically). |
date? | string | Original creation date as an ISO 8601 string (e.g. "2026-06-14T09:00:00Z"). Maps to the DateTime IngestDocumentInput.date. Used for recency ranking and the “published” display. |
id | string | The stable external identifier from the source system — the dedup key within the source. Maps to IngestDocumentInput.externalId. Use the native id (HN objectID, RSS guid, Notion page id), never a value that changes between runs. |
metadata? | Record<string, unknown> | Arbitrary connector-specific JSON. Maps to IngestDocumentInput.metadata. Never put credentials or auth headers here. |
tags? | string[] | Array of string tags. Maps to IngestDocumentInput.tags. |
text? | string | Full plain-text content. Maps to IngestDocumentInput.text. Strip HTML/markup for best search quality. Required unless audioUrl is set. |
title? | string | Document title. Maps to IngestDocumentInput.title. |
url? | string | Canonical link back to the original. Maps to IngestDocumentInput.url. |
ConnectorManifest
Section titled “ConnectorManifest”A connector manifest.json — declares what the connector is, how often it runs,
and which preference fields the user fills in during setup
(connectors/manifest reference).
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
author? | string | Who wrote the connector. |
category? | string | Directory grouping — reading, social, finance, dev, media, etc. |
config? | Record<string, ManifestConfigField> | The preference fields shown in the setup wizard. Preferences only — no credentials. |
description? | string | One-line directory description. |
document_semantics? | ConnectorContentType | Default content type for documents this connector produces. |
icon? | string | A single emoji or an HTTPS URL to a square icon. |
id | string | Stable connector-type id; pattern ^[a-z0-9-]+$. Required. |
kind? | string | What kind of source this is — feed, account, files, api. |
name | string | Human-readable display name. Required. |
needs_browser? | boolean | Whether the connector requires a Playwright browser. Default false. |
schedule? | string | null | Human-readable sync cadence — "every 6 hours", "daily", "on demand", or null for a live-only connector. Optional (default: on demand). |
transport? | string | How the connector reaches the source — http, browser, fs. |
version | string | Semver version string. Required. |
ConnectorSyncResult
Section titled “ConnectorSyncResult”The result of a connector sync: the documents fetched this run and, optionally,
the watermark the source should advance to. A connector may also return a bare
ConnectorDocument[] for convenience — runConnector normalizes that to
{ documents } with no cursor change.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
cursor? | Watermark | The watermark to advance the source cursor to. Omit (or return { type: 'none' }) to leave the cursor unchanged. The cloud only advances if the new value is monotonically ahead (compare-and-swap). |
documents | ConnectorDocument[] | The documents fetched this run, mapping 1:1 onto IngestDocumentInput. |
ManifestConfigField
Section titled “ManifestConfigField”A single field descriptor inside a manifest config object — describes one
preference input shown in the connector’s setup wizard.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
label? | string | Display label in the setup UI. |
placeholder? | string | Optional example text shown in the input. |
type? | string | Input type — text, text[], url, url[], path, number, boolean. |
ManifestValidationResult
Section titled “ManifestValidationResult”The outcome of validateConnectorManifest.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
errors | string[] | Human-readable error messages; empty when valid is true. |
valid | boolean | True when no errors were found. |
RunOptions
Section titled “RunOptions”The options runConnector builds a ctx from. The CLI passes the
source’s stored config and current cursor; tests inject fetch, log, and
now for determinism.
Type Parameters
Section titled “Type Parameters”| Type Parameter | Default type | Description |
|---|---|---|
C | Record<string, unknown> | The shape of the connector’s typed ctx.config preferences. |
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
config? | C | The source’s preference values (no credentials). Defaults to {}. |
cursor? | Watermark | The source’s current watermark. Defaults to { type: 'none' }. |
fetchImpl? | FetchLike | The fetch implementation to expose as ctx.fetch. Defaults to global fetch. |
logSink? | (args: unknown[]) => void | A sink for ctx.log(...) calls. Defaults to collecting into the returned logs. |
now? | () => Date | The clock backing ctx.now(). Defaults to () => new Date(). |
RunResult
Section titled “RunResult”The outcome of runConnector: the validated, deduped documents, the
resolved cursor, and the captured log lines. Mirrors enough of what the cloud
ingest reports for trove connector test to print a useful summary.
Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
cursor | Watermark | The cursor sync returned, or { type: 'none' } if it returned none. |
documents | ConnectorDocument[] | The validated, deduped documents sync returned. |
duplicatesSkipped | number | Count of documents dropped as duplicates of an earlier id. |
logs | unknown[][] | Captured ctx.log(...) lines (when no custom logSink was supplied). |
TroveConnector
Section titled “TroveConnector”The type a connector’s default export satisfies. A connector is an object with
a sync method that fetches from its source and returns documents to index.
Example
Section titled “Example”import { defineConnector } from '@ontrove/sdk';
export default defineConnector({ async sync(ctx) { const res = await ctx.fetch(ctx.config.feedUrl as string); return parse(await res.text()); },});Type Parameters
Section titled “Type Parameters”| Type Parameter | Default type | Description |
|---|---|---|
C | Record<string, unknown> | The shape of the connector’s typed ctx.config preferences. |
Methods
Section titled “Methods”sync()
Section titled “sync()”sync(ctx: ConnectorContext<C>): Promise< | ConnectorSyncResult| ConnectorDocument[]>;The batch entry point. Fetches from the source and returns documents to
index, optionally with a new cursor. May return a bare ConnectorDocument[]
for convenience. Throw a plain Error to fail the run (the Mac app records
the error and retries next tick).
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
ctx | ConnectorContext<C> |
Returns
Section titled “Returns”Promise<
| ConnectorSyncResult
| ConnectorDocument[]>
Type Aliases
Section titled “Type Aliases”ConnectorContentType
Section titled “ConnectorContentType”type ConnectorContentType = "text" | "transcript" | "highlight" | "bookmark";The default content type Trove assigns a document when it omits contentType.
Mirrors the GraphQL ContentType enum surfaced on IngestDocumentInput.
FetchLike
Section titled “FetchLike”type FetchLike = (url: string | URL, init?: RequestInit) => Promise<Response>;The standard fetch signature the SDK exposes on ConnectorContext.fetch.
Matches the platform fetch so existing code ports unchanged.
Parameters
Section titled “Parameters”| Parameter | Type |
|---|---|
url | string | URL |
init? | RequestInit |
Returns
Section titled “Returns”Promise<Response>
Watermark
Section titled “Watermark”type Watermark = | { type: "date"; value: string;} | { max?: string; type: "idSet"; values: readonly string[];} | { type: "none";};A typed watermark describing how a source resumes between syncs. The opaque
Source.cursor string is parsed into one of these (see the cursors-and-sources
guide, watermark types).
date— the source is time-ordered and supports “since <date>” filtering (RSS, most APIs); the newestvalueyou push becomes the next cursor.idSet— the source has monotonic ids but no reliable date filter; advance to the highest id seen (max), optionally tracking a recent id set.none— re-fetch everything each run and rely purely on(source, id)dedup. Always correct, just less efficient.
Functions
Section titled “Functions”defineConnector()
Section titled “defineConnector()”function defineConnector<C>(connector: TroveConnector<C>): TroveConnector<C>;Validate and return a connector definition unchanged.
Using defineConnector(...) (rather than a bare satisfies TroveConnector)
gives an eager runtime check: the CLI calls this when loading a connector
module so a malformed export is rejected before any sync runs. The returned
value is the same object, with full inferred types preserved.
Type Parameters
Section titled “Type Parameters”| Type Parameter | Default type | Description |
|---|---|---|
C | Record<string, unknown> | The shape of the connector’s typed ctx.config preferences. |
Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
connector | TroveConnector<C> | The connector object to validate. |
Returns
Section titled “Returns”The same connector object.
Throws
Section titled “Throws”If connector is not an object with a sync function.
Example
Section titled “Example”export default defineConnector({ async sync(ctx) { const res = await ctx.fetch(ctx.config.feedUrl as string); return parseRss(await res.text()); },});defineSync()
Section titled “defineSync()”function defineSync<C>(sync: (ctx: ConnectorContext<C>) => Promise< | ConnectorSyncResult| ConnectorDocument[]>): TroveConnector<C>;A connector authored inline as a single sync function, for the common case
where the connector has no other members. Equivalent to
defineConnector({ sync }).
Type Parameters
Section titled “Type Parameters”| Type Parameter | Default type | Description |
|---|---|---|
C | Record<string, unknown> | The shape of the connector’s typed ctx.config preferences. |
Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
sync | (ctx: ConnectorContext<C>) => Promise< | ConnectorSyncResult | ConnectorDocument[]> | The connector’s sync(ctx) implementation. |
Returns
Section titled “Returns”A validated TroveConnector wrapping sync.
isCredentialConfigKey()
Section titled “isCredentialConfigKey()”function isCredentialConfigKey(key: string): boolean;Whether a single config key looks like a credential and must be rejected.
Mirrors the cloud’s isSensitiveConfigKey.
Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
key | string | The config key to test. |
Returns
Section titled “Returns”boolean
True if the key matches a credential pattern.
runConnector()
Section titled “runConnector()”function runConnector<C>(connector: TroveConnector<C>, options?: RunOptions<C>): Promise<RunResult>;Run a connector’s sync against a built ctx and collect/validate the result.
This is the harness the CLI drives for trove connector dev / trove connector test: it builds the ConnectorContext from { config, cursor }, runs
sync(ctx), normalizes a bare-array return, validates every document’s
required fields (clear, indexed errors), and dedups by id. Errors thrown by
sync itself propagate unchanged so the CLI can report a failed run exactly as
the Mac app would.
Type Parameters
Section titled “Type Parameters”| Type Parameter | Default type | Description |
|---|---|---|
C | Record<string, unknown> | The shape of the connector’s typed ctx.config preferences. |
Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
connector | TroveConnector<C> | The connector whose sync to run. |
options | RunOptions<C> | The { config, cursor } spec plus test injection points. |
Returns
Section titled “Returns”Promise<RunResult>
The validated, deduped RunResult.
Throws
Section titled “Throws”If sync throws, returns a malformed value, or a document fails validation.
validateConnectorManifest()
Section titled “validateConnectorManifest()”function validateConnectorManifest(manifest: unknown): ManifestValidationResult;Validate a connector manifest.json for trove connector validate.
Checks required fields and the id/version patterns, validates the config
descriptors, and runs the credential-key lint (rejecting credential-shaped
config keys — same spirit as the server-side validateConfig). Returns a
structured result rather than throwing, so the CLI can print all errors at
once.
Parameters
Section titled “Parameters”| Parameter | Type | Description |
|---|---|---|
manifest | unknown | The parsed manifest object to validate. |
Returns
Section titled “Returns”{ valid, errors } — valid: true with an empty errors array on success.