Web SDK reference
Requests, typed services, events, cancellation, and connection lifecycle in @trove/plugin-sdk.
Verified September 11, 2026
On this page
Install and connect#
The template supplies @trove/plugin-sdk as a local package. See SDK setup to add it to an existing project. This reference covers SDK 0.1.0 and Bridge protocol v1.
import { trove } from "@trove/plugin-sdk";
await trove.ready();
console.log(trove.context);
ready(): Promise<void> connects once and shares that connection across subsequent calls. Requests also wait for readiness automatically. Importing the SDK is lazy and safe outside Host; calling the default client or reading context in an ordinary browser raises bridge.host_unavailable.
| Context field | Type / meaning |
|---|---|
curioId, curioVersion | Your Curio’s ID and manifest version |
instanceId | Current UI instance ID |
apiVersion | 1 |
mode | "development" or "production" |
Context is read-only and supplied by Host. It is not a caller identity you can set in request parameters.
Request and service clients#
const host = await trove.call<{
name: "Trove";
version: string;
platform: "macos";
arch: string;
protocol: 1;
}>("host.runtime", "getHostInfo", {}, { timeoutMs: 5_000 });
const storage = trove.service("host.storage");
await storage.call("set", { key: "theme", value: "system" });
| API | Contract |
|---|---|
call<TResult, TParams>(service, method, params?, options?) | Returns Promise<TResult>; omitted parameters become {} |
service(serviceId) | Returns a client with call(method, params?, options?) and subscribe(...) |
service<TContract>(serviceId) | Checks method names and parameter/result types against a TypeScript interface |
self.call(method, params?, options?) | Sends a private request to your Curio’s Backend using @self |
Generic result types describe your expectation; they do not add validation in the Web SDK. Public services are validated by Host against their contracts. The SDK exposes transport primitives, not fabricated helpers such as trove.fs.readText().
interface TextTools {
uppercase(params: { text: string }): Promise<{ text: string }>;
}
const textTools = trove.service<TextTools>(
"dev.example.text-tools.transformer",
);
const result = await textTools.call("uppercase", { text: "hello" });
This example requires an installed provider and a matching services.requires declaration. See service integration. trove.self also requires a Backend that actually implements the requested method; the starter templates do not include one.
Timeouts and cancellation#
CallOptions and SubscribeOptions accept timeoutMs?: number and signal?: AbortSignal. The SDK’s default request timeout is 30,000 ms. An explicit timeout must be finite and between 1 and 2,147,483,647 ms. Host or a service may enforce additional limits.
const controller = new AbortController();
const request = trove.call(
"host.runtime", "echo", { message: "hello" },
{ timeoutMs: 5_000, signal: controller.signal },
);
// Connect this to a cancel button or component cleanup when needed:
// controller.abort();
const result = await request;
Timeout and cancellation reject the promise and send a best-effort cancel message if the request was already sent. They do not roll back a completed side effect. Retry only when your operation is safe to repeat.
Events and cleanup#
const subscription = await trove.subscribe<{ taskId: string; progress: number }>(
"dev.example.text-tools.transformer",
"progress",
{ taskId: "demo" },
(event) => console.log(event.progress),
);
// When the owning screen or task is finished:
await subscription.close();
The provider must implement that event topic and contract. subscribe returns a TroveSubscription with id: string and close(): Promise<void>. Calling close() more than once is safe. An AbortSignal can cancel setup and closes the subscription if aborted after setup. Put cleanup in your framework’s unmount lifecycle, including when a subscription resolves after the component has already unmounted.
disconnect(): void ends the entire client connection, rejects pending calls, and clears subscriptions. Page reload handles this automatically. A disconnected client is terminal; create a new client when an independent reconnection is needed. Do not disconnect the shared trove singleton every time an individual component unmounts.
Structured errors#
import { trove, TroveError } from "@trove/plugin-sdk";
try {
await trove.call("host.runtime", "getHostInfo", {});
} catch (error) {
if (error instanceof TroveError) {
console.error({
code: error.code,
message: error.message,
retryable: error.retryable,
traceId: error.traceId,
});
} else {
console.error(error);
}
}
TroveError extends Error. It carries code, message, optional data, retryable (default false), and optional traceId. Branch on code, not translated message text. Show a useful user message and preserve the trace ID for debugging. Common codes and recovery steps are in troubleshooting.
Custom clients and testing#
createTroveClient({ bridge?, context?, onListenerError? }) creates an independent client. onListenerError receives exceptions thrown by event listeners. TroveBridge implements protocolVersion: 1, connect(listener), send(message), and disconnect().
Use createMockBridge(handlers) with an explicit client for browser tests. Handler keys take the form "service.method"; the mock never becomes the default production transport. See the complete mock example.