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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@datashift/sdk

v0.1.3

Published

Datashift SDK for human-in-the-loop AI agent workflows

Readme

@datashift/sdk

TypeScript SDK for integrating human-in-the-loop checkpoints into AI agent workflows.

Overview

Datashift enables AI agents to submit tasks for human review before committing changes. Perfect for data enrichment workflows where AI agents enhance CRM and database records with external data, and reviewers verify accuracy before updates are applied.

Installation

npm install @datashift/sdk

Quick Start

import {DatashiftRestClient} from '@datashift/sdk';

const datashift = new DatashiftRestClient({
    apiKey: process.env.DATASHIFT_API_KEY!,
});

// AI agent enriches company data from external sources
const enrichedData = {
    companyId: 'acme_corp_123',
    original: {name: 'Acme Corp', industry: 'Unknown'},
    enriched: {
        name: 'Acme Corporation',
        industry: 'Manufacturing',
        employees: 5200,
        revenue: '$1.2B',
        linkedIn: 'https://linkedin.com/company/acme',
    },
    sources: ['LinkedIn', 'Crunchbase', 'SEC Filings'],
};

// Submit enrichment for human verification before updating CRM
const task = await datashift.task.submit({
    queueKey: 'data-enrichment',
    data: enrichedData,
    summary: 'Verify Acme Corp enrichment before CRM update',
});

// Wait for reviewer approval
const result = await datashift.task.waitForReview(task.id, {
    timeout: 300000,    // 5 minutes
    pollInterval: 5000, // Check every 5 seconds
});

// Apply verified changes to CRM
if (result.result.includes('approved')) {
    await updateCRM(enrichedData.companyId, enrichedData.enriched);
}

Authentication

Create an API key in the Datashift Console.

const datashift = new DatashiftRestClient({
    apiKey: 'sk_live_...'
});

REST Client API

Task Operations

// Submit a task
const task = await datashift.task.submit({
    queueKey: 'approvals',
    data: {...},
    summary: 'Short description',
});

// Get task by ID
const task = await datashift.task.get(taskId);

// Get task status (lightweight)
const status = await datashift.task.getStatus(taskId);

// List tasks
const tasks = await datashift.task.list({
    queueId?: string;
    state?: 'pending' | 'queued' | 'completed';
});

// Wait for review
const completed = await datashift.task.waitForReview(taskId, {
    timeout: 300000,       // 5 minutes
    pollInterval: 2000,    // Start at 2s
    maxPollInterval: 30000 // Back off to 30s
});

Queue Operations

// List queues
const queues = await datashift.queue.list();

// Get queue config
const queue = await datashift.queue.get('refund-approvals');

Review Operations

// List reviews with filters
const reviews = await datashift.review.list({
    queueKey: 'approvals',
    reviewerType: 'human',
    from: new Date('2024-01-01'),
    limit: 50,
});

// Get review by ID
const review = await datashift.review.get(reviewId);

Webhook Operations

// Create webhook
const webhook = await datashift.webhook.create({
    url: 'https://your-service.com/webhook',
    events: ['task.completed'],
});
// Save webhook.secret securely!

// List webhooks
const webhooks = await datashift.webhook.list();

// Update webhook
const updated = await datashift.webhook.update(webhookId, {
    url: 'https://new-url.com/webhook',
    events: ['task.completed', 'task.created'],
    active: true,
});

// Rotate webhook secret
const rotated = await datashift.webhook.rotateSecret(webhookId);
// Save rotated.secret securely!

// Send test event
await datashift.webhook.test(webhookId);

// Delete webhook
await datashift.webhook.delete(webhookId);

Webhooks

Webhooks provide async notifications when tasks are completed.

Setting Up a Webhook Endpoint

import express from 'express';
import {verifyWebhookSignature, parseWebhookEvent} from '@datashift/sdk';
import type {WebhookEvent} from '@datashift/sdk';

const app = express();

// IMPORTANT: Use raw body for signature verification
app.post('/webhook/datashift',
    express.raw({type: 'application/json'}),
    (req, res) => {
        const signature = req.headers['x-datashift-signature'] as string;

        // Verify signature
        if (!verifyWebhookSignature(req.body, signature, WEBHOOK_SECRET)) {
            return res.status(401).send('Invalid signature');
        }

        // Parse event
        const event = parseWebhookEvent<WebhookEvent>(req.body);

        if (event.event === 'task.completed') {
            const {task_id, result, review} = event.data;
            handleTaskCompleted(task_id, result, review);
        }

        res.status(200).send('OK');
    }
);

Webhook Events

| Event | Description | |------------------|--------------------------------| | task.completed | Task review has been completed |

Webhook Payload

interface WebhookEvent {
    event: string;      // Event type
    timestamp: string;  // ISO 8601 timestamp
    data: {
        task_id: string;
        queue_key: string;
        state: string;
        result: string[];
        review?: Review;
    };
}

Types

Task

interface Task {
    id: string;
    queue_id: string;
    external_id: string | null;
    state: 'pending' | 'queued' | 'completed';
    data: Record<string, unknown>;
    context: Record<string, unknown>;
    metadata: Record<string, unknown>;
    summary: string | null;
    sla_deadline: string | null;
    completed_at: string | null;
    created_at: string;
    updated_at: string;
    reviews?: Review[];
}

Queue

interface Queue {
    id: string;
    key: string;
    name: string;
    description: string | null;
    review_type: 'approval' | 'labeling' | 'classification' | 'scoring' | 'augmentation';
    review_options: ReviewOption[];
    assignment: 'manual' | 'round_robin' | 'ai_first';
    sla_minutes: number | null;
    deleted_at: string | null;
}

Review

interface Review {
    id: string;
    task_id: string;
    reviewer_id: string | null;
    result: string[];
    data: Record<string, unknown>;
    comment: string | null;
    created_at: string;
}

ListReviewsFilters

interface ListReviewsFilters {
    taskId?: string;
    reviewerId?: string;
    queueKey?: string;
    reviewerType?: 'ai' | 'human';
    from?: Date | string;
    to?: Date | string;
    limit?: number;
    offset?: number;
}

ReviewResult

interface ReviewResult {
    task_id: string;
    result: string[];
    data: Record<string, unknown>;
    completed_at: string;
    review: Review;
}

Error Handling

The SDK provides typed error classes for common error scenarios.

import {
    DatashiftError,
    AuthenticationError,
    NotFoundError,
    ValidationError,
    TimeoutError,
    RateLimitError,
    ServerError,
} from '@datashift/sdk';

try {
    const result = await datashift.task.waitForReview(taskId, {timeout: 60000});
} catch (error) {
    if (error instanceof TimeoutError) {
        console.log('Review not completed in time');
    } else if (error instanceof AuthenticationError) {
        console.log('Invalid credentials');
    } else if (error instanceof NotFoundError) {
        console.log('Task not found');
    } else if (error instanceof RateLimitError) {
        console.log(`Rate limited. Retry after ${error.retryAfter}s`);
    } else if (error instanceof DatashiftError) {
        console.log(`API error: ${error.message}`);
    }
}

Configuration

REST Client Options

interface RestClientConfig {
    apiKey: string;       // Required
    baseUrl?: string;     // Default: 'https://api.datashift.io'
    timeout?: number;     // Request timeout (default: 30000ms)
    retries?: number;     // Retry count (default: 3)
    retryDelay?: number;  // Initial retry delay (default: 1000ms)
}

Wait Options

interface WaitOptions {
    timeout?: number;        // Max wait time (default: 300000ms)
    pollInterval?: number;   // Poll interval (default: 2000ms)
    maxPollInterval?: number; // Max poll interval for backoff (default: 30000ms)
}

Examples

Backend Service with Webhooks

import {DatashiftRestClient, verifyWebhookSignature, parseWebhookEvent} from '@datashift/sdk';
import type {WebhookEvent} from '@datashift/sdk';
import express from 'express';

const datashift = new DatashiftRestClient({
    apiKey: process.env.DATASHIFT_API_KEY!,
});

// Store pending tasks (use Redis/DB in production)
const pendingTasks = new Map<string, (result: any) => void>();

// Submit task and return immediately
async function submitForReview(data: any): Promise<string> {
    const task = await datashift.task.submit({
        queueKey: 'reviews',
        data,
    });
    return task.id;
}

// Webhook handler
const app = express();

app.post('/webhook/datashift',
    express.raw({type: 'application/json'}),
    (req, res) => {
        const signature = req.headers['x-datashift-signature'] as string;

        if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET!)) {
            return res.status(401).send('Invalid signature');
        }

        const event = parseWebhookEvent<WebhookEvent>(req.body);

        if (event.event === 'task.completed') {
            const resolver = pendingTasks.get(event.data.task_id);
            if (resolver) {
                resolver(event.data);
                pendingTasks.delete(event.data.task_id);
            }
        }

        res.status(200).send('OK');
    }
);

app.listen(3000);

Requirements

  • Node.js 18+
  • TypeScript 5.0+ (for type definitions)

License

MIT