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

@fhirust/sdk

v0.4.2

Published

TypeScript SDK for building FHIRust WASM plugins

Readme

@fhirust/sdk

TypeScript SDK for building FHIRust WASM plugins

npm version License: MIT

Build powerful FHIR resource validation, transformation, and enrichment plugins for FHIRust using TypeScript and WebAssembly.


📦 Installation

npm install @fhirust/sdk @bytecodealliance/componentize-js

Requirements:

  • Node.js 18+ or Bun
  • TypeScript 5.0+
  • @bytecodealliance/componentize-js >= 0.14.0 (0.19+ recommended for security fixes)

🚀 Quick Start

1. Create a new plugin

import { FhirPlugin, reject } from "@fhirust/sdk";
import type { Patient } from "@fhirust/sdk/r4";

const plugin = new FhirPlugin("my-validator", "1.0.0");

// Validate Patient before creation
plugin.beforeCreate<Patient>("Patient", (patient, ctx) => {
    if (!patient.name || patient.name.length === 0) {
        return reject("Patient must have at least one name");
    }
    return patient;
});

// Export plugin hooks
const pluginExports = plugin.exports();
export const lifecycle = pluginExports.lifecycle;
export const hooks = pluginExports.hooks;

2. Build the plugin

# Using the SDK CLI
npx fhirust-sdk build

# Or manually with componentize-js
npx jco componentize src/index.ts -o dist/plugin.wasm \
  --wit wit/plugin.wit \
  --world-name fhirust-plugin

3. Deploy to FHIRust server

# config/server.toml
[[plugins.plugins]]
name = "my-validator"
path = "./plugins/my-validator.wasm"
enabled = true

✨ Features

  • Type-safe FHIR resources — Full TypeScript types for FHIR R4 resources
  • Hook-based architecture — Intercept and modify resources before/after CRUD operations
  • Built-in FHIR client — Make authenticated API calls to the FHIR server
  • HTTP client — Fetch data from external APIs with allowlist control
  • Event emission — Send async events for logging, webhooks, and integrations
  • WebAssembly runtime — Sandboxed execution with fine-grained permissions
  • Testing utilities — Built-in test harness for unit testing plugins

📚 API Reference

Core Classes

FhirPlugin

Main plugin class for registering hooks.

import { FhirPlugin } from "@fhirust/sdk";

const plugin = new FhirPlugin(name: string, version: string);

Methods:

  • beforeCreate<T>(resourceType, handler) — Hook before resource creation
  • afterCreate<T>(resourceType, handler) — Hook after resource creation
  • beforeRead(resourceType, handler) — Hook before resource read
  • afterRead<T>(resourceType, handler) — Hook after resource read
  • beforeUpdate<T>(resourceType, handler) — Hook before resource update
  • afterUpdate<T>(resourceType, handler) — Hook after resource update
  • beforeDelete(resourceType, handler) — Hook before resource deletion
  • afterDelete(resourceType, handler) — Hook after resource deletion
  • exports() — Export plugin metadata and hook executor

Helper Functions

reject(message: string): never

Reject a hook operation with an error message.

if (!patient.birthDate) {
  return reject("Patient birth date is required");
}

outcome(issues: OperationOutcomeIssue[]): OperationOutcome

Create a FHIR OperationOutcome for complex validation errors.

return outcome([
  { severity: "error", code: "required", diagnostics: "Missing name" },
  { severity: "warning", code: "business-rule", diagnostics: "Unusual age" }
]);

FHIR Client

Access the FHIR server from within your plugin using plugin.fhir.

plugin.beforeCreate<Patient>("Patient", async (patient, ctx) => {
  // Search for duplicates
  const bundle = await plugin.fhir.search("Patient", {
    identifier: patient.identifier?.[0]?.value
  });

  if (bundle.total && bundle.total > 0) {
    return reject("Duplicate patient identifier");
  }

  return patient;
});

Methods:

  • plugin.fhir.search(resourceType, params) — Search for resources
  • plugin.fhir.read(resourceType, id) — Read a resource by ID
  • plugin.fhir.create(resourceType, resource) — Create a new resource
  • plugin.fhir.update(resourceType, id, resource) — Update a resource
  • plugin.fhir.delete(resourceType, id) — Delete a resource

HTTP Client

Make HTTP requests to external APIs using plugin.http (requires http.fetch permission).

plugin.beforeCreate<Patient>("Patient", async (patient, ctx) => {
  // Verify insurance eligibility via external API
  const response = await plugin.http.post("https://api.insurance.com/verify", {
    body: { memberId: patient.identifier?.[0]?.value }
  });

  if (!response.ok) {
    return reject("Insurance verification failed");
  }

  return patient;
});

Event Emitter

Emit events for async processing using plugin.events.

plugin.afterCreate<Patient>("Patient", (patient, ctx) => {
  plugin.events.emit("patient.created", {
    id: patient.id,
    name: patient.name?.[0]?.family
  });

  return patient;
});

Logger

Structured logging with severity levels using plugin.log.

plugin.beforeCreate<Patient>("Patient", (patient, ctx) => {
  plugin.log.info(`Validating patient: ${patient.id}`);
  plugin.log.warn(`Missing phone number for patient: ${patient.id}`);
  plugin.log.error(`Validation failed for patient: ${patient.id}`);

  return patient;
});

Note: Logger methods accept only string messages. To include structured data, serialize it in the message string using template literals or JSON.stringify().


💡 Examples

Example 1: Patient Name Validator

import { FhirPlugin, reject } from "@fhirust/sdk";
import type { Patient } from "@fhirust/sdk/r4";

const plugin = new FhirPlugin("name-validator", "1.0.0");

plugin.beforeCreate<Patient>("Patient", (patient) => {
  if (!patient.name || patient.name.length === 0) {
    return reject("Patient must have at least one name");
  }

  const hasFamily = patient.name.some(n => n.family);
  if (!hasFamily) {
    return reject("At least one name must include a family name");
  }

  return patient;
});

export const { lifecycle, hooks } = plugin.exports();

Example 2: Auto-Tag Resources

import { FhirPlugin } from "@fhirust/sdk";
import type { Patient } from "@fhirust/sdk/r4";

const plugin = new FhirPlugin("auto-tagger", "1.0.0");

plugin.beforeCreate<Patient>("Patient", (patient) => {
  // Add validation tag
  patient.meta = patient.meta || {};
  patient.meta.tag = patient.meta.tag || [];
  patient.meta.tag.push({
    system: "http://example.org/tags",
    code: "validated",
    display: "Validated by Plugin"
  });

  return patient;
});

export const { lifecycle, hooks } = plugin.exports();

Example 3: Duplicate Detection

import { FhirPlugin, reject } from "@fhirust/sdk";
import type { Patient } from "@fhirust/sdk/r4";

const plugin = new FhirPlugin("duplicate-detector", "1.0.0");

plugin.beforeCreate<Patient>("Patient", async (patient, ctx) => {
  const identifier = patient.identifier?.[0]?.value;
  if (!identifier) return patient;

  try {
    // Search for existing patients with same identifier
    const bundle = await plugin.fhir.search("Patient", {
      identifier: identifier
    });

    if (bundle.total && bundle.total > 0) {
      return reject(`Duplicate patient found with identifier: ${identifier}`);
    }

    return patient;
  } catch (error) {
    plugin.log.error(`Duplicate check failed: ${error}`);
    // Fail open: allow creation if duplicate check fails
    return patient;
  }
});

export const { lifecycle, hooks } = plugin.exports();

Example 4: External API Integration

import { FhirPlugin, reject } from "@fhirust/sdk";
import type { Patient } from "@fhirust/sdk/r4";

const plugin = new FhirPlugin("insurance-verifier", "1.0.0");

plugin.beforeCreate<Patient>("Patient", async (patient, ctx) => {
  const memberId = patient.identifier?.find(
    i => i.system === "http://insurance.org"
  )?.value;

  if (memberId) {
    try {
      // Verify with external insurance API
      const response = await plugin.http.post(
        "https://api.insurance.com/verify",
        { body: { memberId } }
      );

      if (!response.ok) {
        return reject("Insurance verification failed");
      }

      plugin.log.info(`Insurance verified for member: ${memberId}`);
    } catch (error) {
      plugin.log.error(`Insurance verification error: ${error}`);
      return reject("Insurance verification service unavailable");
    }
  }

  return patient;
});

export const { lifecycle, hooks } = plugin.exports();

🧪 Testing

Use the built-in testing utilities:

import { FhirPlugin } from "@fhirust/sdk";
import { createTestContext } from "@fhirust/sdk/testing";
import type { Patient } from "@fhirust/sdk/r4";
import { test } from "node:test";
import assert from "node:assert";

test("validates patient name", async () => {
  const plugin = new FhirPlugin("test", "1.0.0");

  plugin.beforeCreate<Patient>("Patient", (patient) => {
    if (!patient.name?.length) {
      return reject("Name required");
    }
    return patient;
  });

  const ctx = createTestContext();
  const patient: Patient = {
    resourceType: "Patient",
    name: [{ family: "Doe", given: ["John"] }]
  };

  const handler = plugin.getHandler("beforeCreate", "Patient");
  const result = await handler(patient, ctx);

  assert.equal(result.name[0].family, "Doe");
});

Run tests:

npm test

🔧 CLI Commands

The SDK includes a CLI for common tasks:

# Create a new plugin from template
npx fhirust-sdk init my-plugin

# Build plugin to WASM
npx fhirust-sdk build

# Run tests
npx fhirust-sdk test

# Validate plugin manifest
npx fhirust-sdk validate

📝 Configuration

Plugin Permissions

Configure plugin permissions in config/server.toml:

[[plugins.plugins]]
name = "my-plugin"
path = "./plugins/my-plugin.wasm"
enabled = true
permissions = [
  "fhir.read",          # Read FHIR resources
  "fhir.write",         # Create/update FHIR resources
  "utils.log",          # Write to server logs
  "utils.emit-event",   # Emit async events
  "http.fetch"          # Make HTTP requests
]
http_allowlist = [
  "https://api.insurance.com/*",
  "https://api.eligibility.org/*"
]

🤝 Contributing

Contributions are welcome! Please see CONTRIBUTING.md for details.


📄 License

MIT © FHIRust Contributors


🔗 Resources

  • Documentation: https://fhirust.dev/docs/plugins
  • GitHub: https://github.com/wei6bin/fhirust
  • npm: https://www.npmjs.com/package/@fhirust/sdk
  • Issues: https://github.com/wei6bin/fhirust/issues

📌 Version History

See CHANGELOG.md for release notes.