Adapter SDK

adapter sdk

Resources

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.

Inputless resource

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() // → ResourceBinding

Parameterized resource

Add 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" }) // → ResourceBinding

The input schema drives both runtime validation and TypeScript inference. The query function receives the parsed output, not the raw input.

Discovery metadata

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.

Refresh strategies

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.

StrategyBehavior
manual()Fetch on mount and on invalidate() only
poll(interval)Re-fetch on a fixed interval while the page is mounted

Using bindings

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.

How the runtime executes

  1. A page calls schemas({ database: "MAIN" }) — produces a ResourceBinding (declarative, no execution).
  2. The runtime serializes the binding and schedules execution.
  3. The query function runs with the validated input and the adapter context.
  4. Results flow to every component watching that binding.
  5. On poll() resources, the runtime re-executes on the interval. On manual() resources, only invalidate() triggers a re-fetch.

Context

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>.