npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@dvina/sdk

v3.3.124

Published

Type-safe SDK for the Dvina GraphQL API

Downloads

64

Readme

@dvina/sdk

Type-safe SDK for the Dvina GraphQL API. Offline-first architecture with normalized local store (IndexedDB), real-time sync, and framework adapters for Angular and React.

Installation

npm i @dvina/sdk

Workspace Development

From the repository root:

pnpm bootstrap
pnpm --filter @dvina/sdk run generate:workspace
pnpm --filter @dvina/sdk run build

Or use the root shortcut:

pnpm build:sdk

generate:workspace first builds the codegen helper packages, then regenerates SDK sources. build:workspace runs the full generate + build flow in one command.

Peer dependency:

npm i graphql

Framework adapters (optional):

# Angular
npm i @angular/core

# React
npm i react

Quick Start

import { DvinaSdk } from '@dvina/sdk';

const sdk = new DvinaSdk({ token: 'your-auth-token' });

For production apps with token refresh:

const sdk = new DvinaSdk({
	getToken: async () => {
		// return a fresh token from your auth provider
		return await authService.getAccessToken();
	},
});

Options

| Option | Type | Description | | ---------- | -------------------------- | ------------------------------------------------------ | | token | string | Static auth token (scripts, CLI, tests) | | getToken | () => Promise<string> | Async token callback (production apps) | | baseUrl | string | API base URL without protocol. Default: api.dvina.ai | | language | string \| (() => string) | Preferred language for API responses |

Queries

Fetching a Single Entity

const chat = await sdk.chat({ id: 'chat-id' }).fetch();

console.log(chat.id);
console.log(chat.title);

Fetching a Connection (List)

const reports = await sdk.reports({ first: 20 }).fetch();

for (const report of reports.nodes) {
	console.log(report.id, report.name);
}

console.log(reports.totalCount);
console.log(reports.pageInfo.hasNextPage);

Smart Fetch (Including Relations)

Chain relation methods before .fetch() to include related data in a single request:

// Include agent data inline with each chat
const chats = await sdk.chats({ first: 50 }).agent().fetch();

// agent is directly available on each node (no extra request)
for (const chat of chats.nodes) {
	console.log(chat.agent.id, chat.agent.name);
}

// Single entity with includes
const chat = await sdk.chat({ id: 'chat-id' }).agent().fetch();
console.log(chat.agent.name);

Nested includes with variables:

const reports = await sdk.reports({ first: 100 }).insights({ first: 10 }).fetch();

for (const report of reports.nodes) {
	for (const insight of report.insights.nodes) {
		console.log(insight.title);
	}
}

Lazy Relation Fetching

Without Smart Fetch, relations are fetched on demand via method calls:

const chat = await sdk.chat({ id: 'chat-id' }).fetch();

// Separate request for the agent
const agent = await chat.agent().fetch();
console.log(agent.name);

Pagination

Connections follow the Relay cursor specification with built-in pagination helpers:

const reports = await sdk.reports({ first: 20 }).fetch();

// Load next page (appends nodes, updates pageInfo)
if (reports.pageInfo.hasNextPage) {
	await reports.fetchNext();
}

// Load previous page (prepends nodes, updates pageInfo)
if (reports.pageInfo.hasPreviousPage) {
	await reports.fetchPrevious();
}

Utility functions:

import { extractNodes, mergeConnections } from '@dvina/sdk/pagination';

const allNodes = extractNodes(reports);

// Returns a new connection without modifying the originals
const merged = mergeConnections(firstPage, secondPage);

Mutations

// Create
const agent = await sdk.createAgent({
	data: { name: 'My Agent', description: 'An assistant' },
});

// Update
const updated = await sdk.updateReport({
	where: { id: 'report-id' },
	data: { name: 'Updated Name' },
});

// Delete
await sdk.deleteChat({ where: { id: 'chat-id' } });

Mutations automatically update the local store with optimistic updates and cache rules.

Reactive Queries (watch)

Use .watch() instead of .fetch() to get a reactive DvinaQueryRef that emits new values whenever the underlying data changes (via local mutations, delta sync, or cache updates):

const ref = sdk.reports({ first: 20 }).watch();

// Vanilla JavaScript — AsyncIterable
for await (const reports of ref) {
	console.log('Updated:', reports.nodes.length);
}

// Clean up when done
ref.dispose();

DvinaQueryRef properties:

| Property/Method | Description | | --------------- | ------------------------------------------ | | current | Most recently emitted value (or undefined) | | refetch() | Force re-fetch from network | | fetchMore() | Fetch additional pages and merge | | dispose() | Unsubscribe and clean up |

Subscriptions

Real-time data via WebSocket (graphql-ws protocol):

const stream = sdk.sendMessageStream({
	chatId: 'chat-id',
	content: 'Hello!',
});

for await (const event of stream) {
	console.log(event);
}

File Upload

const file = new File(['content'], 'document.pdf', { type: 'application/pdf' });

const result = await sdk.uploadFile(file, 'document.pdf', {
	onProgress: (event) => {
		console.log(`${event.percent}% uploaded`);
	},
});

console.log('File ID:', result.id);

Framework Adapters

Angular

import { toSignal } from '@dvina/sdk/angular';

@Component({
	selector: 'app-reports',
	template: `
		@for (report of reports()?.nodes; track report.id) {
			<div>{{ report.name }}</div>
		}
	`,
})
class ReportsComponent {
	private sdk = inject(DvinaSdk);

	reports = toSignal(this.sdk.reports({ first: 20 }).watch());
}

toSignal automatically disposes the query ref when the component is destroyed via Angular's DestroyRef.

For usage outside an injection context:

const destroyRef = inject(DestroyRef);

// Later, in a method:
this.reports = toSignal(ref, { destroyRef });

dvnResource

A resource primitive inspired by Angular's resource() API. It wraps both Promise (.fetch()) and AsyncIterable (.watch()) loaders into a signal-based interface with status and error tracking.

import { dvnResource } from '@dvina/sdk/angular';

With reactive params — one-shot fetch (Promise):

@Component({ ... })
class UserComponent {
	private sdk = inject(DvinaSdk);
	userId = input.required<string>();

	userResource = dvnResource({
		params: () => ({ id: this.userId() }),
		loader: ({ params }) => this.sdk.user(params).fetch(),
	});

	// template:
	// @if (userResource.isLoading()) { <spinner /> }
	// @if (userResource.value(); as user) { <h1>{{ user.name }}</h1> }
	// @if (userResource.error(); as err) { <p>{{ err.message }}</p> }
}

With reactive params — live query (AsyncIterable):

@Component({ ... })
class ReportsComponent {
	private sdk = inject(DvinaSdk);

	reportsResource = dvnResource({
		params: () => ({ first: 20 }),
		loader: ({ params }) => this.sdk.reports(params).watch(),
	});

	// Value updates automatically whenever the underlying store changes
}

Without params — runs immediately:

configResource = dvnResource({
	loader: () => this.sdk.config().fetch(),
});

The loader return type is detected at runtime:

  • Returns a Promise → resolved once, status becomes 'resolved'
  • Returns an AsyncIterable / DvinaQueryRef → consumed as a live stream, value updates on every emission

When params() returns undefined, the resource enters 'idle' status and the loader does not run. This is useful for conditional fetching:

reportResource = dvnResource({
	params: () => {
		const id = this.reportId();
		return id ? { id } : undefined; // undefined → idle, no request
	},
	loader: ({ params }) => this.sdk.report(params).fetch(),
});

DvnResourceRef API:

| Property/Method | Type | Description | | --------------- | ---------------------------- | --------------------------------------------------------------------- | | value | Signal<T \| undefined> | Most recently resolved value | | status | Signal<DvnResourceStatus> | 'idle' | 'loading' | 'reloading' | 'resolved' | 'error' | | isLoading | Signal<boolean> | true when 'loading' or 'reloading' | | error | Signal<Error \| undefined> | Last error thrown by the loader | | hasValue() | boolean | Whether value() is not undefined | | reload() | void | Re-run the loader with current params | | destroy() | void | Abort/dispose active loader, reset to 'idle' |

Cleanup is automatic via DestroyRef — the loader is aborted (Promise) or disposed (AsyncIterable) when the component is destroyed.

React

import { useLiveQuery } from '@dvina/sdk/react';

function ReportsPage() {
	const reports = useLiveQuery(() => sdk.reports({ first: 20 }).watch(), []);

	if (!reports) return <div>Loading...</div>;

	return (
		<ul>
			{reports.nodes.map((r) => (
				<li key={r.id}>{r.name}</li>
			))}
		</ul>
	);
}

The factory function is memoized internally — the query ref is only created when the dependency array changes. This prevents infinite dispose-subscribe loops that would occur with inline .watch() calls.

// Reactive query — ref recreated when `id` changes
function ReportDetail({ id }: { id: string }) {
	const report = useLiveQuery(() => sdk.report({ id }).watch(), [id]);

	if (!report) return <div>Loading...</div>;
	return <h1>{report.name}</h1>;
}

useLiveQuery automatically disposes the query ref when the component unmounts or when dependencies change.

Error Handling

The SDK provides a typed error hierarchy:

import { DvinaError, DvinaGraphQLError, DvinaNetworkError, DvinaAuthenticationError } from '@dvina/sdk';

try {
	await sdk.chat({ id: 'invalid' }).fetch();
} catch (error) {
	if (error instanceof DvinaAuthenticationError) {
		// 401/403 — redirect to login
	} else if (error instanceof DvinaGraphQLError) {
		// Server returned GraphQL errors
		console.error(error.errors);
	} else if (error instanceof DvinaNetworkError) {
		// Connection failure, timeout, etc.
		console.error(error.status);
	}
}

| Error Class | When | | -------------------------- | ------------------------------------- | | DvinaError | Base class for all SDK errors | | DvinaGraphQLError | GraphQL response contains errors | | DvinaNetworkError | Network failure, timeout, non-OK HTTP | | DvinaAuthenticationError | 401/403 authentication failure |

Architecture

The SDK uses an offline-first, normalized store architecture:

  • Queries are routed through the SyncEngine, which normalizes GraphQL responses into entity tables in IndexedDB (via Dexie).
  • Mutations apply optimistic updates immediately, then reconcile with server responses.
  • Delta Sync maintains a persistent SSE connection that streams entity changes, keeping the local store up to date in real-time.
  • Reactive queries (watch()) are backed by Dexie's liveQuery — any write to a relevant table automatically re-emits.
DvinaSdk
├── Queries/Mutations → HTTP Transport → SyncEngine → IndexedDB (Dexie)
├── Subscriptions     → WebSocket Transport (graphql-ws)
└── Delta Sync        → SSE Transport → IndexedDB (Dexie)

Cleanup

// Stop delta sync
sdk.stopSync();

// Full teardown (stops sync, terminates WebSocket)
sdk.destroy();

License

MIT