adapter sdk
Read-only data sources managed by the runtime.
A resource is a parameterized data source. It describes what data is wanted — the runtime controls execution, refreshing, and cleanup.
No parameters. The query runs with undefined input.
import { defineResource } from "@northgraindata/dsui-adapter-sdk";
import type { MyContext } from "./context.js";
export const status = defineResource({
id: "status",
query: async (_input, ctx: MyContext) => ({
url: ctx.config.baseUrl,
ok: await ctx.client.ping(),
}),
});Call it without arguments to create a binding:
status() // → ResourceBindingAdd a Zod input schema. Binding arguments are validated and typed.
import { defineResource, z } from "@northgraindata/dsui-adapter-sdk";
export const schemas = defineResource({
id: "schemas",
input: z.object({ database: z.string() }),
query: ({ database }, ctx: MyContext) => ctx.client.listSchemas(database),
});schemas({ database: "ANALYTICS" }) // → ResourceBindingThe input schema drives both runtime validation and TypeScript inference. The query function receives the parsed output, not the raw input.
Resources can describe their purpose and access policy for generic consumers,
including AI agents. The host publishes these fields alongside the input schema
through Plugin SDK context.services.resources(serviceId).
defineResource({
id: "sample-orders",
description: "Read up to 10 sample orders from the selected region.",
policy: "preview",
input: z.object({ region: z.string() }),
query: ({ region }, ctx: MyContext) => ctx.client.sampleOrders(region, 10),
});policy is optional and accepts metadata, preview, or sql. Consumers can
disable preview or SQL reads without knowing adapter or resource IDs. Tag every
sample-data/SQL resource, including lower-level aliases. Legacy untagged resources
remain discoverable. These declarations do not enforce read-only execution:
the adapter must enforce query safety, permissions, limits and timeouts itself.
For deep links, a resource may return navigation: [{ label, path, ...identity }]
alongside its data. path is adapter-relative, such as /orders/encoded-id;
encode variable path segments. The AI plugin scopes it to the service route.
Consumers must not guess paths from adapter identities.
Resources default to manual() — fetched on mount and on explicit invalidation only.
import { manual, poll } from "@northgraindata/dsui-adapter-sdk";
defineResource({
id: "warehouses",
query: (_, ctx) => ctx.client.listWarehouses(),
refresh: poll("5s"), // re-fetch every 5 seconds while watched
});poll() accepts "500ms", "5s", "2m", or a raw number of milliseconds.
| Strategy | Behavior |
|---|---|
manual() | Fetch on mount and on invalidate() only |
poll(interval) | Re-fetch on a fixed interval while the page is mounted |
Pass resource bindings to DSUI components via source:
import { Table, Value, Card } from "@northgraindata/dsui-adapter-sdk";
definePage({
path: "/",
render: () => [
Table({ source: schemas({ database: "MAIN" }) }),
Value({ source: status(), field: "url" }),
Card({ content: Value({ source: status(), field: "ok" }) }),
],
});The runtime executes the query, manages polling, and delivers results to the component. The resource never runs in the page — it runs in the adapter host.
schemas({ database: "MAIN" }) — produces a ResourceBinding (declarative, no execution).poll() resources, the runtime re-executes on the interval. On manual() resources, only invalidate() triggers a re-fetch.The second argument to query is the adapter context — the same object returned by context() in defineAdapter. Use it to access clients, configuration, and anything built during adapter initialization.
export const tables = defineResource({
id: "tables",
input: z.object({ schema: z.string() }),
query: ({ schema }, ctx: MyContext) =>
ctx.client.listTables(schema),
});If your adapter has no context, the second argument is Record<string, never>.