adapter sdk
Routes that compose components and bindings into views.
A page is a route. It receives typed params, query params, and store access — and returns a tree of DSUI components.
import { definePage, PageHeader, Table } from "@northgraindata/dsui-adapter-sdk";
export const overview = definePage({
path: "/",
render: () => [
PageHeader({ title: "Overview" }),
Table({ source: status() }),
],
});Segments like :database become typed params:
export const schemaPage = definePage({
path: "/databases/:database/schemas/:schema",
render: ({ params }) => [
PageHeader({ title: `${params.database} / ${params.schema}` }),
Table({ source: tables({ database: params.database, schema: params.schema }) }),
],
});params is Record<string, string> — every segment is a string. The route pattern is validated at definition time (must start with /).
Access the URL query string via query:
export const searchPage = definePage({
path: "/search",
render: ({ query }) => [
PageHeader({ title: `Search: ${query.get("q") ?? ""}` }),
Table({ source: searchResults({ query: query.get("q") ?? "" }) }),
],
});query is a standard URLSearchParams instance.
Pages compose DSUI components into trees. Components accept resources as source and actions as action:
import {
definePage, PageHeader, Section, Table, Button, Card, Value,
} from "@northgraindata/dsui-adapter-sdk";
definePage({
path: "/warehouses",
render: () => [
PageHeader({
title: "Warehouses",
actions: [
Button({ label: "Refresh", action: flush() }),
],
}),
Section({
title: "Compute",
content: Table({
source: warehouses(),
rowActions: [
{
label: "Suspend",
action: suspendWarehouse({ warehouse: "row.name" }),
when: { field: "status", equals: "RUNNING" },
},
],
}),
}),
Section({
title: "Summary",
content: Card({
content: Value({ source: warehouseSummary(), field: "total" }),
}),
}),
],
});Pages receive a stores accessor for reading and subscribing to store state:
import { defineStore } from "@northgraindata/dsui-adapter-sdk";
const filterStore = defineStore({
id: "filters",
scope: "page",
state: { status: "all" as string },
actions: ({ set }) => ({
setStatus: (status: string) => set({ status }),
}),
});
definePage({
path: "/warehouses",
stores: [filterStore],
render: ({ stores }) => {
const { status } = stores.use(filterStore); // subscribes to changes
return [
Table({ source: warehousesFiltered({ status }) }),
];
},
});| Method | Behavior |
|---|---|
stores.use(def) | Returns state + actions. Subscribes the page — re-renders on state change. |
stores.get(def) | Returns state only. No subscription — reads the current snapshot. |
Use use when the UI depends on the store. Use get when you need a value but the page shouldn’t re-render when it changes.
Pass stores to definePage to create page-scoped instances. These are destroyed when the page scope is disposed:
definePage({
path: "/editor",
stores: [editorStore],
render: ({ stores }) => {
const { content } = stores.use(editorStore);
return [CodeBlock({ label: "Editor", value: content })];
},
});Page-scoped stores get a fresh instance per page mount. Adapter-scoped stores survive across page navigation.
A page can return a single node or an array:
// single
render: () => PageHeader({ title: "Home" }),
// array
render: () => [
PageHeader({ title: "Home" }),
Section({ title: "...", content: Table({ source: data() }) }),
],The runtime matches a URL to a page path, creates a PageScope, and calls render(). The scope owns store instances and handles disposal. Components receive the serialized tree and render it in the browser.
const scope = instance.createPageScope("/databases/MAIN/schemas/PUBLIC");
await scope.ready();
const nodes = scope.render(); // PageNode[]