Back to Changelog
Featurev1.0.0

Node.js / TypeScript SDK v1.0 Released

Official Node.js and TypeScript SDK for OMOPHub - typed client with discriminated { data, error } returns, 37 methods across 8 resources, full FHIR Resolver support from day one.

The Node.js / TypeScript SDK is out, joining Python and R on the official client list. Same 37 methods, same 8 resources, full FHIR Resolver coverage from day one - but with a TypeScript-first design and a few Node-idiomatic touches you don't get in the other SDKs.

Installation

npm install @omophub/omophub-node

Requires Node 22 or 24. The package ships as ESM with TypeScript declarations.

Quick Start

import { OMOPHub } from '@omophub/omophub-node';

// Uses OMOPHUB_API_KEY env var by default
const client = new OMOPHub();

// Search across vocabularies
const { data: results } = await client.search.basic('type 2 diabetes', {
  vocabularyIds: ['SNOMED'],
  standardConcept: 'S',
});

// Get a concept by ID
const { data: concept } = await client.concepts.get(201826);
console.log(concept?.concept_name); // "Type 2 diabetes mellitus"

// Map between vocabularies
const { data: mappings } = await client.mappings.get(201826, {
  targetVocabulary: 'ICD10CM',
});

Errors as Values, Not Exceptions

The SDK's distinguishing design choice: every network call returns a discriminated Response<T> = { data, error, meta, headers }. Errors don't throw - they land in error as a typed object, and TypeScript narrows data to non-null in the success branch:

const { data, error } = await client.concepts.get(999999999);
if (error) {
  if (error.name === 'not_found') {
    console.log('Concept does not exist');
  } else if (error.name === 'rate_limit_exceeded') {
    console.log(`Retry after ${error.retryAfter}s`);
  }
  return;
}
// TypeScript knows `data` is non-null here
console.log(data.concept_name);

16 stable error codes (not_found, restricted_api_key, rate_limit_exceeded, validation_error, timeout_error, connection_error, ...) - switch on error.name for programmatic handling. The thrown-exception world of Python try/except is gone; TypeScript's exhaustiveness checking gates every branch instead.

Built-In Retries & Backoff

Production-ready retry behaviour out of the box - no extra config needed:

  • Retries on 429, 502, 503, 504, and transient network errors.
  • Full-jitter exponential backoff: 500ms → 1s → 2s → 4s → 8s, capped.
  • Retry-After honoured up to 60 seconds (parsed strictly per RFC 9110).
  • POST/PATCH retried only when Idempotency-Key is set, except for 429 - pre-processing rejections are retry-safe regardless of method.
const client = new OMOPHub({
  maxRetries: 5,        // default: 3
  timeoutMs: 60_000,    // default: 30s per call
});

Async Iterators for Pagination

for await walks pages without manual bookkeeping:

// Stream concepts page by page
for await (const concept of client.search.basicIter('insulin', { pageSize: 100 })) {
  console.log(concept.concept_name);
}

// Or collect everything with a page cap
const { data, pagesFetched } = await client.search.basicAll('insulin', {
  pageSize: 100,
  maxPages: 5,
});

The iterator throws OMOPHubIteratorError mid-stream on a failed page - you choose whether to retry the page or bail. basicAll collects partial results plus the error list so you can recover from a transient failure without re-walking the whole corpus.

FHIR Resolver Included

The FHIR Resolver is in v1.0.0 from day one - including the v1.8.0 FHIR-to-OMOP IG features (Value-as-Concept, user-selected priority, on_unmapped sentinel):

const { data } = await client.fhir.resolve({
  system: 'http://snomed.info/sct',
  code: '44054006',
  resourceType: 'Condition',
});
console.log(data?.resolution.standard_concept.concept_name);
console.log(data?.resolution.target_table); // "condition_occurrence"

// Accepts either flat form (above) or nested coding form:
await client.fhir.resolve({
  coding: { system: 'http://snomed.info/sct', code: '44054006' },
  resourceType: 'Condition',
});

resolveBatch (1-100 codings) and resolveCodeableConcept (1-20 codings with OHDSI vocabulary preference) round out the FHIR surface.

Vocabulary Versioning

Pin to a specific vocabulary release for reproducible research:

const client = new OMOPHub({ vocabVersion: '2025.2' });

// Or pin per call:
const { data } = await client.concepts.get(201826, { vocabRelease: '2025.2' });

Full Surface Parity

37 methods across 8 resources, matching Python:

client.concepts      - get, getByCode, batch, suggest, related, relationships, recommended
client.search        - basic, basicIter, basicAll, advanced, autocomplete, semantic,
                       semanticIter, semanticAll, bulkBasic, bulkSemantic, similar
client.vocabularies  - list, get, stats, domainStats, domains, conceptClasses, concepts
client.domains       - list, concepts
client.hierarchy     - get, ancestors, descendants
client.relationships - get, types
client.mappings      - get, map
client.fhir          - resolve, resolveBatch, resolveCodeableConcept

Plus standalone helpers: omophubFhirUrl(version) for the FHIR Terminology Service base URL, and getApiKey() / setApiKey() / hasApiKey() for env-backed configuration.

Resources

Issues and contributions welcome on GitHub.