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

@meetdewey/typescript-sdk

v2.5.0

Published

TypeScript client for the Dewey API

Readme

dewey

CI

TypeScript/JavaScript client for the Dewey API. See the full API reference for details on all endpoints and types.

Installation

npm install dewey
# or
pnpm add dewey

Quick start

import { DeweyClient } from 'dewey'

const client = new DeweyClient({ apiKey: 'dwy_live_...' })

// Create a collection (projectId from your Dewey dashboard → Project Settings)
const col = await client.collections.create({
  projectId: 'proj_...',
  name: 'My Docs',
})

// Upload a document
const doc = await client.documents.upload(col.id, file, { filename: 'report.pdf' })

// Query
const results = await client.retrieval.query(col.id, 'What is the refund policy?')

// Research (SSE streaming)
for await (const event of client.research.stream(col.id, 'Summarise key findings')) {
  if (event.type === 'chunk') process.stdout.write(event.content)
  if (event.type === 'done') console.log('\nSources:', event.sources)
}

Constructor

new DeweyClient({ apiKey: string, baseUrl?: string })

| Option | Default | Description | | --------- | -------------------------------- | ------------------------- | | apiKey | — | dwy_live_… or dwy_test_… | | baseUrl | https://api.meetdewey.com/v1 | Override for self-hosting |

Resources

client.collections

| Method | Description | | ------------------------------- | ------------------------------------------------ | | create(input) | Create a collection | | list() | List collections | | get(id) | Get by ID | | update(id, input) | Update a collection | | delete(id) | Delete a collection | | stats(id) | Document count, storage, section/chunk/claim counts | | recomputeSummaries(id) | Re-run AI section summarization | | recomputeCaptions(id) | Re-run AI captioning for images and tables | | recomputeClaims(id) | Re-extract factual claims (clears existing) |

update() accepts: name, visibility, chunkSize, chunkOverlap, description, enableSummarization, enableCaptioning, enableReranking, enableDeduplication, llmModel, instructions. All fields are optional; llmModel and instructions accept null to clear the field.

// Set research instructions for a collection
await client.collections.update(collectionId, {
  instructions: 'All figures are in USD unless stated otherwise.',
})

// Clear instructions
await client.collections.update(collectionId, { instructions: null })

// Get collection statistics
const stats = await client.collections.stats(collectionId)
console.log(`${stats.docCount} docs, ${stats.totalClaimsCount} claims`)

client.documents

| Method | Description | | ---------------------------------------------- | -------------------------------------- | | upload(collectionId, file, opts?) | Multipart upload | | uploadMany(collectionId, files, opts?) | Bulk upload via presigned S3 URLs | | requestUploadUrl(collectionId, input) | Get a presigned S3 URL | | confirm(collectionId, documentId) | Confirm presigned upload | | list(collectionId) | List documents | | get(collectionId, documentId) | Get document | | getMarkdown(collectionId, documentId) | Get rendered Markdown (string) | | retry(collectionId, documentId) | Retry a failed document | | delete(collectionId, documentId) | Delete a document |

upload() accepts File, Blob, Buffer, or a Node.js ReadableStream.

uploadMany() is the recommended approach for large datasets. Each file is uploaded directly to S3 (bypassing the API server), so there are no payload-size limits. Files that match an existing document's hash are deduplicated automatically.

import { readdir, readFile } from 'node:fs/promises'
import path from 'node:path'

const dir = './reports'
const names = await readdir(dir)
const files = await Promise.all(
  names
    .filter(n => n.endsWith('.pdf'))
    .map(async n => ({ file: await readFile(path.join(dir, n)), filename: n }))
)

const docs = await client.documents.uploadMany(collectionId, files, {
  concurrency: 10,
  onProgress: (doc, n, total) => console.log(`${n}/${total} ${doc.filename}`),
})

client.sections

| Method | Description | | ---------------------------------------- | ----------------------------- | | list(collectionId, documentId) | List sections for a document | | get(sectionId) | Get section (with content) | | getChunks(sectionId) | Get chunks for a section | | scan(collectionId, query, opts?) | Full-text section scan |

client.retrieval

| Method | Description | | ----------------------------------- | ---------------------------- | | query(collectionId, q, opts?) | Hybrid semantic + FTS search |

client.research

| Method | Description | | ----------------------------------------- | ---------------------------------------- | | stream(collectionId, q, opts?) | SSE research stream → AsyncIterable |

stream() options: depth ('quick'|'balanced'|'deep'|'exhaustive'), model (OpenAI model ID).

client.agents

Invoke saved agents defined in the dashboard. orgId and projectId are UUIDs (not slugs); agentSlug is the human-readable slug shown in dashboard URLs (e.g. 'qa-test').

| Method | Description | | ------------------------------------------------------------- | ---------------------------------------------------- | | invokeSync(orgId, projectId, agentSlug, { query }) | Buffered run → Promise<AgentInvokeResult> | | stream(orgId, projectId, agentSlug, { query }) | SSE run → AsyncIterable<AgentRunEvent> |

// Buffered — resolves once the run terminates
const result = await client.agents.invokeSync(orgId, projectId, 'qa-test', {
  query: 'What changed in 2023?',
})
console.log(result.response)
for (const s of result.sources) {
  console.log(`- ${s.filename} § ${s.sectionTitle}`)
}

// Streaming — yields tool calls, tokens, and the final done event
for await (const event of client.agents.stream(orgId, projectId, 'qa-test', {
  query: 'What changed in 2023?',
})) {
  if (event.type === 'chunk') process.stdout.write(event.content)
  if (event.type === 'done') console.log(`\n${event.sources.length} sources`)
}

AgentRunEvent is a discriminated union over type: 'run_started' | 'tool_call' | 'tool_result' | 'chunk' | 'done' | 'error' | 'warning'.

client.claims

| Method | Description | | ----------------------------------------------- | -------------------------------------------- | | mapStream(collectionId) | SSE stream of all claims with UMAP coordinates | | listByDocument(documentId, opts?) | Claims extracted from a specific document |

mapStream() yields ClaimMapEvent objects: { type: 'progress', pct }, { type: 'done', total, claims }, or { type: 'error', message }.

for await (const event of client.claims.mapStream(collectionId)) {
  if (event.type === 'done') {
    console.log(`${event.total} claims`)
    for (const claim of event.claims) {
      console.log(`[${claim.importance}] ${claim.text}`)
    }
  }
}

// Per-document claims (fast, no SSE)
const { claims } = await client.claims.listByDocument(documentId, { minImportance: 3 })

client.contradictions

| Method | Description | | --------------------------------------------------------------- | --------------------------------------------------- | | list(collectionId, opts?) | List detected contradictions | | detect(collectionId) | Trigger async contradiction detection run | | getLatestRun(collectionId) | Poll status of the latest detection run | | dismiss(collectionId, contradictionId) | Mark a contradiction as ignored | | applyInstruction(collectionId, contradictionId, instruction?) | Apply resolution; appends to collection instructions |

// Trigger detection, then poll
const run = await client.contradictions.detect(collectionId)
console.log('Run ID:', run.runId)

// Later: poll status
const status = await client.contradictions.getLatestRun(collectionId)
console.log(status.status, status.contradictionsFound)

// List active contradictions
const { items } = await client.contradictions.list(collectionId, { status: 'active' })
for (const c of items) {
  console.log(c.severity, c.explanation)
  // Apply the suggested resolution
  await client.contradictions.applyInstruction(collectionId, c.id)
}

client.duplicates

Fuzzy document deduplication. Identifies near-duplicate documents by measuring how much content they share, marks one member of each cluster as canonical, and excludes near-duplicates from retrieval and contradiction detection. Must be enabled per-collection with client.collections.update(id, { enableDeduplication: true }).

| Method | Description | | ------------------------------------------------------------ | -------------------------------------------------------------------- | | detect(collectionId) | Trigger async dedup run across all ready documents | | getLatestRun(collectionId) | Poll status of the latest dedup run | | list(collectionId, opts?) | List duplicate groups with members and coverage percentages | | promoteCanonical(collectionId, groupId, canonicalDocumentId) | Promote a different member to canonical; old canonical becomes near_duplicate | | disband(collectionId, groupId) | Disband a group; all former members rejoin retrieval as distinct |

// Enable on a collection (one-time)
await client.collections.update(collectionId, { enableDeduplication: true })

// Trigger detection, then poll
const run = await client.duplicates.detect(collectionId)
const status = await client.duplicates.getLatestRun(collectionId)
console.log(status.status, status.duplicateGroupsCreated)

// Review groups
const { items } = await client.duplicates.list(collectionId)
for (const group of items) {
  for (const m of group.members) {
    if (m.relationship === 'near_duplicate') {
      console.log(`${m.filename} covers ${Math.round((m.coverageToCanonical ?? 0) * 100)}% of canonical`)
    }
  }
}

client.providerKeys

| Method | Description | | ----------------------------- | ------------------------------ | | create(projectId, input) | Add a provider API key | | list(projectId) | List provider keys | | delete(projectId, keyId) | Delete a provider key |

Error handling

All methods throw DeweyError on non-2xx responses:

import { DeweyError } from 'dewey'

try {
  await client.collections.get('unknown-id')
} catch (err) {
  if (err instanceof DeweyError) {
    console.error(err.status, err.message) // e.g. 404 "Collection not found"
  }
}

Presigned upload flow

For single files or when you need manual control, use the low-level presigned URL flow. For bulk ingestion, prefer uploadMany() which handles this automatically with concurrency.

import { createHash } from 'node:crypto'

const data = await fs.readFile('data.pdf')
const contentHash = createHash('sha256').update(data).digest('hex')

// 1. Request a presigned URL
const { documentId, uploadUrl } = await client.documents.requestUploadUrl(
  collectionId,
  { filename: 'data.pdf', contentType: 'application/pdf', fileSizeBytes: data.byteLength, contentHash },
)

// 2. PUT the file bytes directly to S3 (no auth header needed)
await fetch(uploadUrl, { method: 'PUT', body: data, headers: { 'Content-Type': 'application/pdf' } })

// 3. Confirm to trigger ingestion
const doc = await client.documents.confirm(collectionId, documentId)