@fatagnus/dink-sdk
v2.32.0
Published
TypeScript SDK for Dink edge mesh platform
Maintainers
Readme
@fatagnus/dink-sdk
TypeScript SDK for the Dink edge mesh platform.
This SDK is for Lite Edge - edges that require constant connectivity. For offline-first applications, use @fatagnus/dink-sync instead.
| Edge Type | SDK | Use Case |
|-----------|-----|----------|
| Lite Edge | ✅ This SDK | Always-connected IoT, RPC services |
| Offline-First Edge | @fatagnus/dink-sync | Offline data with auto-sync |
| Full Edge | Not yet available | Local NATS services (planned) |
Installation
npm install @fatagnus/dink-sdkQuick Start (createEdgeWorker)
The fastest way to start an edge service:
import { createEdgeWorker } from '@fatagnus/dink-sdk/edge';
import { createServices } from './generated/registry.js';
import { SensorServiceImpl } from './sensor-impl.js';
await createEdgeWorker({
services: createServices({ sensor: new SensorServiceImpl() }),
labels: { region: 'us-east', type: 'sensor' },
});Credentials resolve automatically from DINK_EDGE_KEY or DINK_API_KEY environment variables. Graceful shutdown on SIGINT/SIGTERM is built-in.
For more control, see the EdgeClient section below.
Quick Start (EdgeClient)
Edge Client
Connect an edge device to the Dink platform:
import { EdgeClient } from '@fatagnus/dink-sdk';
// With embedded URL keys (recommended), no serverUrl needed!
const edge = new EdgeClient({
apiKey: process.env.DINK_API_KEY!, // Contains embedded URL + credentials
});
// Edge ID and App ID are extracted from the API key
console.log(`Edge ID: ${edge.getEdgeId()}`); // e.g., "my-edge-device"
console.log(`App ID: ${edge.getAppId()}`); // e.g., "my-app"
await edge.connect();
// Expose a service
await edge.exposeService({
name: 'sensor',
version: '1.0.0',
definition: {
name: 'sensor',
version: '1.0.0',
handleRequest: async (method, data) => {
if (method === 'read') {
return { temperature: 22.5 };
}
throw new Error(`Unknown method: ${method}`);
},
},
});Center Client
Connect from a center/cloud service to discover and call edge services:
import { CenterClient } from '@fatagnus/dink-sdk';
const center = new CenterClient({
serverUrl: 'nats://localhost:4222',
apiKey: process.env.DINK_API_KEY,
});
await center.connect();
// Discover edges
const edges = await center.discoverEdges();
// Call a service on an edge (edge ID comes from discovery)
const result = await center.call({
edgeId: edges[0].id, // e.g., "my-edge-device"
service: 'sensor',
method: 'read',
data: {},
});
console.log(result); // { temperature: 22.5 }Error Types
Typed errors for structured error handling:
import { ConnectionError, AuthError, ServiceError, TimeoutError, ConfigError } from '@fatagnus/dink-sdk';| Error | Code | Fields | Use Case |
|-------|------|--------|----------|
| DinkError | varies | code | Base class for all SDK errors |
| ConnectionError | CONNECTION_ERROR | | NATS connection issues |
| AuthError | AUTH_ERROR | | Invalid/expired/revoked keys |
| ServiceError | SERVICE_ERROR | status, service, method | RPC call failures |
| TimeoutError | TIMEOUT | timeoutMs | Request timeouts |
| ConfigError | CONFIG_ERROR | | Missing required configuration |
Peer RPC (Edge-to-Edge)
Edges in the same group can call each other's services directly:
import { EdgeClient } from '@fatagnus/dink-sdk';
const edge = new EdgeClient({ apiKey: process.env.DINK_EDGE_KEY! });
await edge.connect();
// Expose service for peer calls
await edge.exposePeerService(handler);
// Call a specific peer's service
const caller = edge.groupCaller('my-group');
const client = new SensorServiceClient('target-edge', caller);
const temp = await client.ReadTemperature({});
// Call any peer in the group (load-balanced)
const broadcastCaller = edge.groupBroadcastCaller('my-group');Generated typed clients work unchanged with groupCaller() — same ServiceCaller interface as CenterClient.
Loading Credentials
Use loadDinkEnv() to load credentials from the .dink/ directory (managed by the dink env CLI) with automatic fallback to DINK_SERVER / DINK_API_KEY environment variables.
Note: This is Node.js only (uses
node:fs,node:path). Available via subpath import@fatagnus/dink-sdk/envor from the main entry point.
import { loadDinkEnv } from '@fatagnus/dink-sdk/env';
import { CenterClient, EdgeClient } from '@fatagnus/dink-sdk';
// Load current environment (from .dink/.current) or fall back to env vars
const env = await loadDinkEnv();
const center = new CenterClient({ serverUrl: env.server, apiKey: env.apiKey });
const edge = new EdgeClient({ apiKey: env.apiKey });Load a specific named environment:
const env = await loadDinkEnv('staging'); // reads .dink/staging.yamlCredential resolution order:
- Named env —
.dink/<name>.yaml(when a name is passed) - Current env —
.dink/.current→.dink/<current>.yaml - Env vars —
DINK_SERVER/DINK_API_KEY(CI/Docker fallback)
Error handling:
import { DinkEnvError } from '@fatagnus/dink-sdk/env';
try {
const env = await loadDinkEnv();
} catch (e) {
if (e instanceof DinkEnvError) {
// e.code === 'NO_ENVIRONMENT' — no .dink/ dir and no env vars set
// e.code === 'NOT_FOUND' — named env file missing
}
}This integrates with dink env CLI commands for managing environments. In CI/Docker where no .dink/ directory exists, set DINK_SERVER and DINK_API_KEY as environment variables — the same code works without changes.
API Key Formats
Dink supports two API key formats for edge authentication:
Embedded URL Format (Recommended)
The new format embeds the dinkd server URL directly in the key, enabling easier onboarding where edge agents only need a single API key to connect:
import { EdgeClient } from '@fatagnus/dink-sdk';
// No serverUrl needed - it's embedded in the key!
const edge = new EdgeClient({
apiKey: process.env.DINK_API_KEY!,
});The embedded key format is a base64-encoded JSON object:
base64({"url": "nats://dinkd.example.com:4222", "secret": "dk_..."})Keys created via the Convex SDK (createEdgeKey) or the test utilities automatically use this format.
Legacy Format
The legacy format is a plain string without embedded URL:
dk_<env>_ed_<appID>_<edgeID>_<random>For legacy keys, you must provide serverUrl explicitly:
const edge = new EdgeClient({
serverUrl: 'nats://localhost:4222',
apiKey: 'dk_prod_ed_myapp_myedge_abc123',
});Parsing Keys
Use parseEdgeKey to inspect a key and extract its components:
import { parseEdgeKey } from '@fatagnus/dink-sdk';
// Parse an embedded URL key
const parsed = parseEdgeKey(embeddedKey);
console.log(parsed.serverUrl); // 'nats://dinkd.example.com:4222'
console.log(parsed.secret); // 'dk_prod_ed_myapp_myedge_abc123'
console.log(parsed.appId); // 'myapp'
console.log(parsed.edgeId); // 'myedge'
// Parse a legacy key
const legacy = parseEdgeKey('dk_prod_ed_myapp_myedge_abc123');
console.log(legacy.serverUrl); // undefined (no embedded URL)
console.log(legacy.secret); // 'dk_prod_ed_myapp_myedge_abc123'URL Override
Even with embedded URL keys, you can override the URL in the config:
// Override the embedded URL (e.g., for local development)
const edge = new EdgeClient({
apiKey: productionKey,
serverUrl: 'nats://localhost:4222', // Takes precedence over embedded URL
});Handling Kick Commands
Edges can be forcibly disconnected by the server (e.g., for key revocation, maintenance, or security). Register a callback to handle kick events:
import { EdgeClient } from '@fatagnus/dink-sdk';
const edge = new EdgeClient({ apiKey: process.env.DINK_API_KEY! });
await edge.connect();
// Register kick handler (optional but recommended)
const unsubscribe = edge.onKick((reason: string) => {
console.log(`Received kick command: ${reason}`);
// Clean up resources, save state, etc.
// The edge will automatically disconnect after this callback
});
// Later, if you want to stop listening:
unsubscribe();When kicked, the edge client will:
- Call your
onKickcallback (if registered) - Gracefully disconnect from the server
- Clean up all service subscriptions
Note: To kick an edge from your Convex backend, use kickEdge from @fatagnus/dink-convex.
Subpath Exports
You can also import specific clients directly:
import { EdgeClient } from '@fatagnus/dink-sdk/edge';
import { CenterClient } from '@fatagnus/dink-sdk/center';
import { loadDinkEnv } from '@fatagnus/dink-sdk/env';Naming Rules
App IDs, edge IDs, app names, and service names must follow these rules to ensure compatibility with NATS subjects:
Allowed characters:
- Alphanumeric:
a-z,A-Z,0-9 - Hyphens:
- - Colons:
:
Not allowed:
- Underscore
_(used as key delimiter) - Dot
.(NATS hierarchy separator) - Asterisk
*(NATS wildcard) - Greater than
>(NATS tail wildcard) - Dollar sign
$(NATS system subjects) - Spaces and whitespace
Examples:
// ✓ Valid names
'my-app'
'sensor-42'
'namespace:service'
'MyApp123'
// ✗ Invalid names
'my_app' // underscore not allowed
'my.app' // dot not allowed
'my app' // space not allowed
'sensor*' // asterisk not allowedRequirements
- Node.js >= 18.0.0
- A running dinkd server
Documentation
For full documentation, visit https://github.com/ozanturksever/dink
License
MIT
