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

@eka-care/medical-records-ts-sdk

v1.2.1

Published

TypeScript SDK for Eka Care Medical Records API

Readme

@eka-care/medical-records-ts-sdk

TypeScript SDK for the Eka Care Medical Records API. Offline-first, works in both browser and Electron renderer.


Table of Contents

  1. Installation
  2. Quick Start
  3. Configuration
  4. Authentication
  5. Records
  6. Cases
  7. Vitals & Parameter Trends
  8. Reactive UI Updates
  9. Logging
  10. Offline Support
  11. Electron Setup
  12. Error Handling
  13. Document Types
  14. Running the Demo

1. Installation

npm install @eka-care/medical-records-ts-sdk

2. Quick Start

import { MedicalRecordsClient } from '@eka-care/medical-records-ts-sdk';

const sdk = new MedicalRecordsClient({
  baseUrl: '',
  defaultHeaders: {
    'client-id': 'your-client-id',
    'flavour':   'your-flavour',
    'b-tid':     'your-business-id',   // business tenant ID — sent on every API request
  },
  // cache defaults to true — IndexedDB offline cache is enabled unless you pass `cache: false`
  onError: (err) => console.error(err),
});

sdk.setAuthToken('eyJhbGciOi...');

const docs = await sdk.listDocuments({ bid: 'your-business-id', patientId: 'oid' });

3. Configuration

new MedicalRecordsClient(config: SDKConfig)

| Field | Type | Required | Description | |---|---|---|---| | defaultHeaders | Record<string, string> | Effectively yes | Required Eka headers — see below | | baseUrl | string | No | Override the API base URL. Pass '' for same-origin. Takes precedence over environment | | vaultBaseUrl | string | No | Override the vault base URL (used by getParameterTrend). Pass '' for same-origin | | environment | SDKEnvironment | No | SDKEnvironment.Prod = api.eka.care, SDKEnvironment.Dev = api.dev.eka.care. Ignored if baseUrl is set. Default: Prod | | cache | boolean | No | IndexedDB offline cache. Defaults to true. Pass false to run network-only | | transport | TransportMode | No | TransportMode.Native = browser fetch (default). TransportMode.Bridge = Electron IPC | | onError | (err: unknown) => void | No | Called on every unhandled SDK error (error is still re-thrown) | | onUnauthorized | () => Promise<string \| undefined> \| string \| undefined | No | Called on 401 — return a fresh token to auto-retry once | | onLog | (log: SDKLog) => void | No | Receives every structured log event emitted by the SDK |

Note: cache no longer takes an oid. Patient scope is passed per call via patientId (see Records). logLevel has been removed — every event flows through onLog (see Logging).

Required headers

defaultHeaders: {
  'client-id': 'doc-web',       // app identifier
  'flavour':   'ekascribe-web', // app variant
  'b-tid':     'BID',           // business tenant ID — sent on every API request
}

b-tid (business tenant ID) is required by the Eka API edge for routing and access control. It is merged into every HTTP request automatically.

Note: bid (business ID) is also passed per SDK call — it scopes IndexedDB queries client-side. Both carry the same value.

Full config example

import { MedicalRecordsClient, SDKEnvironment, TransportMode } from '@eka-care/medical-records-ts-sdk';

const sdk = new MedicalRecordsClient({
  environment: SDKEnvironment.Prod,   // or set baseUrl / vaultBaseUrl explicitly
  defaultHeaders: {
    'client-id': 'doc-web',
    'flavour':   'ekascribe-web',
    'b-tid':     'BID',
  },
  cache: true,
  transport: TransportMode.Native,
  onLog: (log) => console.log(`[${log.eventName}] ${log.status}`, log.params),
  onError: (err) => console.error('[SDK]', err),
  onUnauthorized: async () => await refreshToken(),
});

4. Authentication

sdk.setAuthToken('eyJhbGciOi...');  // set after login
sdk.setAuthToken(undefined);         // clear (falls back to cookie auth)

Sent as Authorization: Bearer <token> on every request. On 401, onUnauthorized is called — return a fresh token to auto-retry the failed request once.


5. Records

Every records method requires bid and patientId:

const BID = 'your-business-id';
const OID = 'logged-in-user-oid';

Linked profiles (primary + linked OIDs)

patientId is the primary OID — the profile that owns writes (add/edit/delete use it for the X-Pt-Id header). To also surface records/cases from linked profiles (e.g. dependents), pass linkedPatientIds. Read and list/group methods then return the union across [patientId, ...linkedPatientIds], and the background sync fans out one fetch per OID (each with its own X-Pt-Id). Omit linkedPatientIds for single-profile behavior — nothing changes.

const docs = await sdk.listDocuments({
  bid: BID,
  patientId: OID,                       // primary — owns writes
  linkedPatientIds: ['dependent-oid-1', 'dependent-oid-2'], // also read these
});

Supported on: listDocuments, listLocalDocuments, groupDocuments, getAllTags, listCases, listLocalCases. Writes (addDocument, editDocument, deleteDocument, createCase, …) always use the primary patientId.


List Documents

Delta sync — fetches only records newer than the local watermark.

import { SortBy } from '@eka-care/medical-records-ts-sdk';

// All documents
const docs = await sdk.listDocuments({ bid: BID, patientId: OID });

// With limit and sort
const docs = await sdk.listDocuments({
  bid: BID, patientId: OID,
  limit: 20,                        // default: no limit
  sortBy: SortBy.CreatedAt,         // SortBy.UpdatedAt (default) | SortBy.CreatedAt
});

// Filter by document type
const labReports = await sdk.listDocuments({
  bid: BID, patientId: OID,
  filter: { documentType: 'lr' },   // client-side filter by type code
});

// Filter by case
const caseRecords = await sdk.listDocuments({
  bid: BID, patientId: OID,
  filter: { caseId: 'case-uuid' },
});

// Stale-while-revalidate — show cached instantly, update when fresh arrives
const docs = await sdk.listDocuments({
  bid: BID, patientId: OID,
  onStale: (cached) => setDocs(cached),
});
setDocs(docs);

// Force a full refresh — ignore the local watermark and re-fetch everything
const docs = await sdk.listDocuments({ bid: BID, patientId: OID, forceRefresh: true });

// Local DB only — no network call
const docs = await sdk.listLocalDocuments({ bid: BID, patientId: OID });

ListDocumentsFilter fields:

| Field | Type | Description | |---|---|---| | documentType | string | Filter by document type code ('lr', 'ps' etc.) — client-side | | caseId | string | Filter to records belonging to a case — client-side | | tags | string[] | Only records that have all of these tags — client-side | | fileType | string | Filter by file type ('IMG', 'PDF', 'HTML') — client-side | | maxFileTypeFetch | number | Max documents to collect when fileType is set. Default: 3 | | updatedAfter | number | Only records updated after this epoch — sent to server as u_at__gt |

Each returned DocumentItem also carries is_smart / is_analyzing (AI-report state), sync_state (local sync status when served from cache), and total_vitals / out_of_range_vitals (parameter counts extracted from the document).


Group Documents

Groups records from local DB — no network call.

import { GroupBy, DateGroupFormat, DateField } from '@eka-care/medical-records-ts-sdk';

// Group by document type
const groups = await sdk.groupDocuments({
  bid: BID, patientId: OID,
  groupBy: GroupBy.DocumentType,
});
// → [{ key: 'lr', label: 'Lab Report', records: [...] }, ...]

// Group by month (default)
const groups = await sdk.groupDocuments({
  bid: BID, patientId: OID,
  groupBy: GroupBy.Date,
  dateFormat: DateGroupFormat.Month,   // Day | Month (default) | Year
  dateField: DateField.DocumentDate,   // DocumentDate (default) | CreatedAt
});
// → [{ key: '2025-05', label: 'May 2025', records: [...] }, ...]

GroupDocumentsOptions:

| Field | Type | Default | Description | |---|---|---|---| | groupBy | GroupBy | required | DocumentType or Date | | dateFormat | DateGroupFormat | Month | Granularity when groupBy: Date | | dateField | DateField | DocumentDate | Which date to group by | | sortBy | SortBy | UpdatedAt | Sort within each group | | filter | ListDocumentsFilter | — | Same filters as listDocuments |


Download a Document

Cache-first — returns blobs from IndexedDB if previously fetched, otherwise downloads from S3 and caches.

const files = await sdk.downloadFile({ bid: BID, patientId: OID, documentId: 'abc-uuid' });

for (const file of files) {
  console.log(file.blob);       // Blob — PDF / image / HTML
  console.log(file.fileType);   // DocumentFileType.IMG | .PDF | .HTML
  console.log(file.filename);   // e.g. 'abc12345_0.pdf'
  console.log(file.fromCache);  // true = served from IndexedDB, zero network
}

// Trigger browser download
const a = document.createElement('a');
a.href = URL.createObjectURL(files[0].blob);
a.download = files[0].filename;
a.click();

Get Smart Report

Cache-first — returns the AI-extracted smart report from IndexedDB if available, otherwise fetches from API.

const report = await sdk.getSmartReport({
  bid: BID, patientId: OID,
  documentId: 'abc-uuid',
});

if (report) {
  console.log(report.verified);    // AI-verified items
  console.log(report.unverified);  // AI-unverified items
}
// null = document has no smart report

Describe a Document

Returns file URLs, smart report, tags.

const detail = await sdk.describeDocument({
  bid: BID, patientId: OID,
  documentId: 'abc-uuid',
});

// detail.files[0].asset_url  — signed S3 URL
// detail.smart_report        — AI-extracted fields
// detail.tags                — user tags

Upload a Document

addDocument runs the full two-step flow — obtains presigned S3 URLs then uploads every file.

const file = input.files[0];

await sdk.addDocument({
  bid: BID, patientId: OID,
  batchRequests: [{
    documentType: 'lr',
    documentDate:  Math.floor(Date.now() / 1000),
    cases: ['case-uuid'],                        // optional — assign to case
    files: [{ contentType: 'application/pdf', file_size: file.size }],
  }],
  files: [[file]],
  filenames: [[file.name]],
});

Need finer control? obtainAuthorization (step 1 — presigned URLs) and uploadFile (step 2 — one file to S3) are exposed separately.

Offline: record + blob saved to IndexedDB as upload_failure. Auto-retried on next sdk.sync().

File constraints (also exported as UPLOAD_CONSTRAINTS):

| | Limit | |---|---| | Max batch items | 5 | | Max files per batch | 10 | | Image max | 10 MB | | PDF max | 25 MB | | Supported types | image/jpeg image/jpg image/png application/pdf |


Edit a Document

Local-first — patches DB immediately, syncs to server in background.

await sdk.editDocument({
  bid: BID, patientId: OID,
  documentId: 'abc-uuid',
  data: {
    documentType: 'ps',                 // new document type
    documentDate: 1748000000,           // new document date (epoch)
    cases: ['case-id-1'],               // link to cases (replaces existing)
    tags:  ['blood', 'routine'],        // tags (replaces existing)
  },
});

Delete a Document

Soft-deletes locally (hidden immediately), hard-deletes after server confirms. Also removes the document from any linked cases in local DB.

await sdk.deleteDocument({ bid: BID, patientId: OID, documentId: 'abc-uuid' });

Tags

// Every unique tag across all local documents — sorted alphabetically
const allTags = await sdk.getAllTags({ bid: BID, patientId: OID });

// Tags for one document (from local DB)
const tags = await sdk.getDocumentTags({ bid: BID, patientId: OID, documentId: 'abc-uuid' });

Tags are written via editDocument({ data: { tags: [...] } }) (replaces the full set).


6. Cases

Cases act as folders for records. A record can belong to multiple cases.

List Cases

const cases = await sdk.listCases({ bid: BID, patientId: OID });

// Local DB only:
const cases = await sdk.listLocalCases({ bid: BID, patientId: OID });

// Records inside a case:
const docs = await sdk.listLocalDocuments({
  bid: BID, patientId: OID,
  filter: { caseId: 'case-uuid' },
});

Create a Case

await sdk.createCase({
  bid: BID, patientId: OID,
  data: {
    id:           crypto.randomUUID(),
    display_name: 'Follow-up visit',
    type:         'OP',                             // EM | IP | OP | HH
    occurred_at:  Math.floor(Date.now() / 1000),
  },
});

Update a Case

await sdk.updateCase({
  bid: BID, patientId: OID,
  caseId: 'case-uuid',
  data: { display_name: 'New name', type: 'IP', occurred_at: 1748000000 },
});

Delete a Case

Deletes from server and removes the case from all linked records' cases in local DB.

await sdk.deleteCase({ bid: BID, patientId: OID, caseId: 'case-uuid' });

7. Vitals & Parameter Trends

Fetch the trend (all readings over time) for a single vital/lab parameter — e.g. Hemoglobin. Hits the vault host. Network-only — not cached.

import { VitalResultFilter } from '@eka-care/medical-records-ts-sdk';

const trend = await sdk.getParameterTrend({
  bid: BID, patientId: OID,
  parameterId: 'lb-2139115007',        // eka parameter id
  filter: VitalResultFilter.All,       // All (default) | High | Low | Normal
});

console.log(trend.name);        // 'Hemoglobin (Hb)'
console.log(trend.unit);        // 'g/dL'
for (const log of trend.vitals_info) {
  console.log(log.date, log.val, log.display_result); // newest first
  console.log(log.document_id);                       // pass to describeDocument
}

8. Reactive UI Updates

Subscribe to DB change events — the UI auto-updates on any write (upload, edit, delete, sync).

// Subscribe once (e.g. in useEffect)
const unsub = sdk.subscribe('documents:changed', () => {
  sdk.listLocalDocuments({ bid, patientId }).then(setRecords);
});

const unsubCases = sdk.subscribe('cases:changed', () => {
  sdk.listLocalCases({ bid, patientId }).then(setCases);
});

// Cleanup on unmount
return () => { unsub(); unsubCases(); };

Events:

  • documents:changed — fired after any record write (upload, edit, delete, sync)
  • cases:changed — fired after any case write

Pending sync count:

const { documents, cases } = await sdk.getPendingCounts();
// documents — count of unsynced records (uploading / edited / archived)
// cases     — count of unsynced cases (not yet created or edited on server)

Last server refresh:

sdk.sourceRefreshedAt;   // Unix epoch of the last background refresh, or null before the first listDocuments

9. Logging

Every internal SDK operation emits a structured SDKLog event through your onLog callback — there is no log-level filtering; you receive all events and decide what to keep.

import type { SDKLog } from '@eka-care/medical-records-ts-sdk';

const sdk = new MedicalRecordsClient({
  defaultHeaders: { /* ... */ },
  onLog: (log: SDKLog) => {
    // Forward to your logging / analytics system
    console.log(`[${log.eventName}] ${log.status}`, log.params);
    // e.g. Mixpanel.track(log.eventName, { ...log.params, status: log.status })
  },
});

SDKLog shape:

interface SDKLog {
  eventName:   string;              // Mixpanel-ready, e.g. "Records_TS_SDK_CREATE"
  eventType:   EventType;           // create | read | update | delete
  status:      EventStatus;         // success | failure
  platform:    EventPlatform;       // database | network
  entityType:  EventEntityType;     // records | cases
  params?:     Record<string, unknown>;  // structured data (counts, document IDs, error details)
  message?:    string;              // optional human-readable description
  patientOid?: string;              // OID the event is scoped to
  bid?:        string;              // business / tenant ID the event is scoped to
  timestamp:   number;              // Unix milliseconds
}

EventType, EventStatus, EventPlatform, and EventEntityType are exported enums.


10. Offline Support

The SDK stores everything in IndexedDB and syncs in the background.

What happens offline

| Action | Offline behavior | |---|---| | listDocuments | Returns cached records immediately | | listCases | Returns cached cases immediately | | addDocument | Saves record + blob as upload_failure — retried on reconnect | | editDocument | Patches DB (isEdited: true) — synced on reconnect | | deleteDocument | Soft-deletes (isArchived: true) — hard-deleted on reconnect | | createCase | Saves locally (isRemoteCreated: false) — created on reconnect |

Sync order

Sync runs in dependency order: case creates → record uploads → (case edits, case deletes, record edits, record deletes) in parallel.

// Auto-runs on every SDK init (page load)
// Manual trigger:
window.addEventListener('online', () => sdk.sync());
await sdk.sync();

Clear cache

// Clear all locally cached data for one business/tenant (records, blobs, tags, reports, cases)
await sdk.clearDbForBid(BID);

To run without any local persistence, construct the client with cache: false.


11. Electron Setup

IndexedDB works natively in the Electron renderer process. Only the transport differs:

import { MedicalRecordsClient, TransportMode } from '@eka-care/medical-records-ts-sdk';

// Electron
new MedicalRecordsClient({ transport: TransportMode.Bridge, defaultHeaders: {...} });

// Browser
new MedicalRecordsClient({ transport: TransportMode.Native, defaultHeaders: {...} });

Bridge routes Eka API calls through window.networkApi (IPC → main → net.fetch). S3 uploads always use fetch() directly — the IPC bridge cannot carry binary data.


12. Error Handling

import {
  EkaCareApiError,
  UploadFailedError,
  StorageLimitExceededError,
} from '@eka-care/medical-records-ts-sdk';

try {
  await sdk.addDocument({ ... });
} catch (err) {
  if (err instanceof UploadFailedError) {
    // Auth succeeded but S3 upload failed — record saved as upload_failure
    // sdk.sync() retries automatically
    console.error('Failed IDs:', err.failures.map(f => f.documentId));
  } else if (err instanceof StorageLimitExceededError) {
    // Server rejected the upload — the account's storage limit is reached (HTTP 403).
    // The local temp record is removed automatically; no retry is possible.
    console.error('Account storage limit reached', err);
  } else if (err instanceof EkaCareApiError) {
    console.error(err.message, err.statusCode, err.responseBody);
  }
}

editDocument, deleteDocument, createCase, updateCase, deleteCase never throw on network failure — local state is always updated and server sync retries automatically.


13. Document Types

Document types are opaque string codes owned by the server/your app; the SDK treats them as plain strings (it no longer ships label constants). Commonly used codes:

| Code | Label | |---|---| | ps | Prescription | | lr | Lab Report | | dc | Discharge Summary | | vc | Vaccine Certificate | | in | Insurance | | iv | Invoice | | sc | Scan | | op | Other |

Pass a code wherever a document type is expected — e.g. filter: { documentType: 'lr' }, batchRequests: [{ documentType: 'lr', ... }], editDocument({ data: { documentType: 'ps' } }). Map codes to display labels in your own app (see packages/ui for an example).


14. Running the Demo

packages/ui is a React playground — not published.

# Install
npm install && cd packages/ui && npm install

# Credentials — edit packages/ui/src/example/sdk/client.ts
export const OID         = 'your-oid';
export const LINKED_OIDS = [];              // optional linked/dependent OIDs
export const BID         = 'your-business-id';

# Start
cd packages/ui && npm run dev
# Open https://test.eka.care:5173

Building

npm run build       # TypeScript → dist/

Publishing

npm version patch
npm publish --access public