Projects

simcapture-sdk

SDK for SimCapture, the clinical simulation recording and assessment platform. It exposes its REST API as a typed TypeScript client.

Each entity has its own namespace (sc.Course, sc.Reservation, …) returning navigable handles, and its builders validate before touching the network.

Features

  • One namespace per entity, with lookup by id or by name: named() matches exactly and tells you when there is no match (NotFoundError) or more than one (AmbiguousMatchError, carrying the ids to disambiguate)
  • Navigable entities: reservation.organization() fetches on demand, and .raw exposes the full API record, including what the SDK doesn’t model
  • Fluent builders: save() validates locally and reports every missing field in a single ValidationError, instead of one round-trip per error
  • Value objects: TimeWindow for time windows (half-open overlap, explicit UTC), Location, Attachment
  • all() walks the pages internally and concatenates; it stops only on an empty page, and throws rather than handing back half a list if the server never returns one
  • Authentication with token caching and transparent retry on 401; concurrent calls share a single in-flight /auth
  • Typed errors under SimCaptureError, carrying the real upstream status instead of a blanket 400
  • Hexagonal architecture: the domain only knows ports (HttpClient, TokenStore), so tests run without network and you can inject your own transport
  • Dual ESM/CJS build with generated .d.ts types and automated npm publishing through GitHub Actions

Tech

TypeScript, Bun, axios, GitHub Actions.

Install

npm install simcapture-sdk

Usage example

import { SimCapture, TimeWindow, Location, Reservation, SimCaptureError } from "simcapture-sdk";

const sc = new SimCapture({
  apiUrl: process.env.SIMCAPTURE_API!,
  inventoryUrl: process.env.SIMCAPTURE_INVENTORY_API!,
  credentials: {
    username: process.env.SIMCAPTURE_USER!,
    password: process.env.SIMCAPTURE_PASSWORD!,
    clientSubdomain: process.env.SIMCAPTURE_SUBDOMAIN!,
  },
});

const org  = await sc.Organization.named("School of Medicine");
const room = new Location({ name: "P3-H1" });
const date = new TimeWindow("2026-06-05").start().at("23:00").end().at("23:10");

const reservation = await new Reservation()
  .titled("Test reservation")
  .inOrganization(org)   // handle, UUID or name
  .at(room)
  .when(date)
  .save();

await reservation.organization();    // navigation: fetched on demand
reservation.date.durationMinutes();

try {
  await sc.Reservation.get("bad-id");
} catch (error) {
  if (error instanceof SimCaptureError) console.error(error.status, error.message);
}

Motivation

The two-way integration between the Interdisciplinary Center for Advanced Simulation platform and SimCapture needed the same client across several microservices. Extracting it into a versioned package removed the duplicated authentication logic and left the API responses typed in a single place.