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

@welshare/sdk

v0.4.0

Published

Primitives, schemas, utilities for interacting with Welshare's sovereign health data sharing platform

Downloads

184

Readme

Welshare shared library functions

This SDK contains provides functions, types, schemas and utilities to interact with Welshare's sovereign health data sharing protocol. It includes FHIR resource handling, Nillion data storage abstractions, API clients and validation schemas.

If you're just looking for an easy way to submit data from your own frontend, the @welshare/react package might be a better fit.

Use this SDK if you want to build infrastructure to manage, authenticate and profile users yourself. The bare minimum requirement is to support a signer function that users use to deterministically derive storage keypairs from their control wallets.

The package wraps Nillion's secretvaults / nuc / blindfold libraries. Some of which have server side dependencies (pino-pretty) that can create quirky side effects when running in browsers if not deliberately treeshaken. Make sure to not expose server facing features in this library (i.e. code that makes use of Nillion "standard" collections)

We're using tshy to build ESM compatible outputs. CommonJS is not supported.

You can find compiled API docs here: https://docs.welshare.app/sdk-docs/

Overview

Welshare API Client

This Welshare barrel object export contains functions that talk to the Welshare backend infrastructure. While user data is stored in owned collections that conceptually aren't directly under our control, the application data (e.g. questionnaire definitions) is stored on Nillion collections that we have write control over.

  • WelshareApi:

    • fetchWelshareApp() - Fetches the AppRecord of a given applicationId
    • fetchDelegation() - users call this to request a delegation NUC that allows them to access to owned collections.
    • submitData() - A facade that helps submitting (questionnaire) data correctly. Fetches apps and delegations in the background, interpolates fields with client side knowledge, and pushes data to Nillion nodes.
    • submitBinaryData() - Stores encrypted binary file metadata in Nillion. See Binary File Uploads below.
    • fetchS3WriteDelegation() - Requests a presigned S3 URL for uploading encrypted files.
    • fetchS3ReadDelegation() - Requests a presigned S3 URL for downloading encrypted files.
    • resolveStorageKey() - Converts welshare:// URLs to storage keys.
  • Environment configuration:

    • WELSHARE_API_ENVIRONMENT - Pre-configured environments (production, staging, preview, development)
    • resolveEnvironment() - Resolve environment configuration with optional overrides
    • WelshareApiEnvironment - Type definition for environment config

Authentication Tools

  • JWT handling: createJWTForStorageKeys(), verifyJWT()
  • Key derivation: deriveKey() (with types Context, KeyId, UserSecret)
  • Storage keys: deriveApplicationKeypair(), deriveStorageKeypair(), verifyAuthProof()

FHIR Resources

  • FHIR namespace (Fhir):
    • validateQuestionnaire() - Validate FHIR Questionnaire resources
    • validateQuestionnaireResponse() - Validate FHIR QuestionnaireResponse resources
    • wrapFhirResourceForNillion() - Wrap FHIR resources for Nillion insertion
    • fhirDateRegex, fhirDateTimeRegex, fhirBase64BinaryRegex - Validation regex patterns
  • FHIR types: FhirCoding, FhirCodeableConcept, FhirQuantity, FhirSimpleQuantity, FhirRange, FhirRatio, FhirPeriod, FhirAttachment, FhirSampledData
  • Standard codes (StandardCodes namespace) - FHIR standard code definitions. This is VERY helpful to refer to commonly used health conditions and observations and they frequently are found in user facing questionnaires.

Nillion Integration

  • Nillion namespace (Nillion):
    • Keypair - Keypair class from @nillion/nuc (this will be replaced with a signer interface soon)
    • makeUserClient() - Create a SecretVault UserClient
    • summarizeUploadResults() - Processes an array of redundant "upload" result that's returned by cluster nodes to one single result
  • Nillion types: CollectionType, NillionClusterConfig, NillionByNodeName, NillionDid, NillionUuid, BlindfoldFactoryConfig

General Utilities

  • Base64 encoding/decoding: base64urlEncode(), base64urlDecode(), base64urlDecodeUtf()
  • Hex utilities: bytesToHex(), hexToBytes() (re-exported from viem)
  • UUID generation: makeUid()

Encryption Utilities

Browser-compatible AES-256-GCM encryption utilities for file handling:

  • generateRandomAESKey() - Generate a random 256-bit AES-GCM key
  • encryptFile(file, key) - Encrypt a file with an AES key, returns encrypted data and IV
  • encodeEncryptionKey(key, iv) - Export key and IV to a storable EncryptionKey format
  • decrypt(encryptedData, encryptionKey) - Decrypt data using an encoded encryption key
  • decodeEncryptionKey(encryptionKey) - Decode an EncryptionKey back to raw bytes
  • Types: EncryptionKey, Algorithm, ALGORITHM

Note: Encryption utilities can be imported from the main package or from the lightweight /encryption entry point that has no Nillion dependencies:

// Option 1: Import from main package (includes all SDK features)
import { encryptFile, decrypt, type EncryptionKey } from "@welshare/sdk";

// Option 2: Import from /encryption (lightweight, no Nillion deps)
import {
  encryptFile,
  decrypt,
  type EncryptionKey,
} from "@welshare/sdk/encryption";

Validation Schemas

All schemas are exported as Zod schemas for validation:

  • AppSchema - Application configuration
  • QuestionnaireSchema - FHIR Questionnaire
  • QuestionnaireResponseSchema - FHIR QuestionnaireResponse
  • PatientSchema - root FHIR Patient data
  • ObservationSchema - FHIR Observation
  • ObservationHelpers - Helper functions for observations
  • ConditionSchema - FHIR Condition
  • ConditionHelpers - Helper functions for conditions
  • BinaryFilesSchema - Binary files
  • NotificationsSchema - Notifications

Types

  • Session types: SessionKeyAuthMessage, AuthorizedSessionProof, SessionKeyData
  • Validation types: InsertableSubmission, InterpolateSocials, ValidatableSchema

Submitting Questionnaire Responses

The submitData() function is the primary method for submitting questionnaire response data to Welshare. It handles fetching application configuration, requesting delegations, validating data, and storing it on Nillion.

Quick Start

This assumes that your app helps your users to manage their keypairs, connects to wallets and signs messages. If you're looking for a more high level integration option, check out our dedicated React integration library: @welshare/react.

Deriving User Keypairs with a Signer Function

Before users can submit data, they need to derive a dedicated storage keypair that's used to authenticate against the Nillion nodes without disclosing the controlling key pair. Welshare uses deterministic key derivation based on signed messages from the user's wallet.

import { deriveStorageKeypair, Keypair } from "@welshare/sdk";
import { createWalletClient, custom } from "viem";
import { mainnet } from "viem/chains";

// Connect to user's wallet (e.g., MetaMask)
const walletClient = createWalletClient({
  chain: mainnet,
  transport: custom(window.ethereum),
});

const [userAddress] = await walletClient.getAddresses();

// Define a signer function that requests signatures from the wallet
const signMessageAsync = async (message: string): Promise<string> => {
  const signature = await walletClient.signMessage({
    account: userAddress,
    message,
  });
  return signature;
};

// Derive the storage keypair deterministically

const keypair: Nillion.Keypair = await deriveStorageKeypair(
  signMessageAsync,
  userAddress
);

// The keypair can now be used for all Welshare operations
console.log("User did:key : ", Keypair.keypairKeyDid(keypair).didString);

Important Notes:

  • The keypair must not leave the client environment at any time!
  • The signer function must return a valid signature for the provided message
  • The same signer will always derive the same keypair
  • Users only need to sign once per session (you can cache the derived keypair)

Submitting a QuestionnaireResponse

For this example to work, you must register an Application and a Questionnaire resource first. Use their ids in this example code.

import { WelshareApi, resolveEnvironment } from "@welshare/sdk";

// Using the keypair and applicationId from the previous example
const environment = resolveEnvironment("production");

// Prepare your FHIR QuestionnaireResponse
const questionnaireResponse = {
  resourceType: "QuestionnaireResponse",
  status: "completed",
  questionnaire: "questionnaire-id",
  authored: new Date().toISOString(),
  item: [
    {
      linkId: "1",
      text: "What is your age?",
      answer: [
        {
          valueInteger: 35,
        },
      ],
    },
    {
      linkId: "2",
      text: "Do you have any allergies?",
      answer: [
        {
          valueBoolean: true,
        },
      ],
    },
  ],
};

// Submit the data
const result = await WelshareApi.submitData(
  keypair,
  {
    collectionType: "QuestionnaireResponse",
    data: questionnaireResponse,
  },
  environment,
  applicationId
);

console.log("Submitted successfully:", result.insertedUid);

Under the hood

submitData() orchestrates the entire submission process:

  • Fetches the application configuration via fetchWelshareApp()
  • Requests a delegation NUC via fetchDelegation()
  • Validates the data against the appropriate schema
  • Wraps the FHIR resource for Nillion storage
  • Writes to the user's owned collection on Nillion
  • Grants the builder application read access to the submitted data

The function automatically handles client-side field interpolation and ensures data is correctly formatted for storage.

Binary File Uploads

The SDK provides functions to upload encrypted binary files (images, documents) to an S3 compatible storage bucket that's hosted by Welshare. Files are AES-256-GCM encrypted client-side before they're leaving the user's environment. Meta file information (mime types, sizes, file names) are stored on the BinaryFile collection on Nillion.

The symmetric encryption keys are threshold encrypted an distributed across Nillion cluster nodes; a single node cannot recover a key. When anchoring a user owned binary file document on Nillion, we're also adding an ACL entry that allows us to decipher the data on behalf of the submitting application, but technically users could withhold that allowance or withdraw it at any time.

Quick Start

This assumes that your app helps your users to manage their keypairs, connects to wallets and signs messages. If you're looking for a more high level integration option, check out our dedicated React integration library: @welshare/react.

Uploading binary files

The simplest way to upload a file is using uploadAndEncryptFile(), which handles the entire flow (generates a new encryption key, encrypts, uploads, and stores metadata):

import { WelshareApi } from "@welshare/sdk";

// Upload a file with one function call
const result = await WelshareApi.uploadAndEncryptFile(
  keypair,
  file, // File object from input or drag-drop
  {
    reference: `questionnaire/${questionnaireId}/photo`,
    applicationId: "my-app-id",
  },
  "production"
);

console.log("File uploaded:", result.insertedUid);
console.log("File URL:", result.url);

// Use in a FHIR QuestionnaireResponse attachment
const attachment = {
  id: result.insertedUid,
  contentType: file.type,
  size: file.size,
  title: file.name,
  url: result.url,
};

Low-level upload (for more control)

If you need more control over the upload process, you can use the lower-level functions:

import { WelshareApi, resolveEnvironment, Keypair } from "@welshare/sdk";
import {
  generateRandomAESKey,
  encryptFile,
  encodeEncryptionKey,
} from "@welshare/sdk/encryption";

const environment = resolveEnvironment("production");

// 1. Get presigned URL for S3 upload
const { presignedUrl, uploadKey } = await WelshareApi.fetchS3WriteDelegation(
  keypair,
  {
    reference: "questionnaire/abc/photo",
    fileName: file.name,
    fileType: file.type,
  },
  environment
);

// 2. Encrypt and upload file to S3
const cryptoKey = await generateRandomAESKey();
const { encryptedData, iv } = await encryptFile(file, cryptoKey);
const encryptionKey = await encodeEncryptionKey(cryptoKey, iv);

await fetch(presignedUrl, {
  method: "PUT",
  body: encryptedData,
  headers: { "Content-Type": file.type },
});

// 3. Store metadata on Nillion
// This call doesn't disclose the key to a single server - it's threshold-encrypted
// and distributed among cluster nodes.
const { insertedUid } = await WelshareApi.submitBinaryData(
  keypair,
  {
    encryption_key: JSON.stringify(encryptionKey),
    reference: "questionnaire/abc/photo",
    file_name: file.name,
    file_size: file.size,
    file_type: file.type,
    controller_did: Keypair.keypairKeyDid(keypair).didString, //Nillion 2.0 requires did:key as a method
    url: `welshare://${uploadKey}`,
  },
  environment,
  applicationId
);

API Side Effects

  • uploadAndEncryptFile(): Combines all steps below into one convenient function.
  • fetchS3WriteDelegation() / fetchS3ReadDelegation(): Makes HTTP requests to the Welshare API (/auth/delegate/storage). Requires a valid self-signed JWT. Returns short-lived presigned URLs (15 sec expiry).
  • submitBinaryData(): Calls fetchDelegation() internally, then writes to Nillion storage. Grants builder read/execute access to the uploaded file metadata.

Creating React Hooks for Binary Uploads

If you're building a React application that manages keypairs directly, you can create a custom hook using the SDK functions. Here's an example implementation:

import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import {
  Nillion,
  WelshareApi,
  resolveEnvironment,
  type WelshareApiEnvironment,
  type WelshareEnvironmentName,
} from "@welshare/sdk";
import { decrypt, type EncryptionKey } from "@welshare/sdk/encryption";

export interface UseBinaryUploadsOptions {
  keypair: Nillion.Keypair | null | undefined;
  environment: WelshareApiEnvironment | WelshareEnvironmentName;
}

export const useBinaryUploads = (options: UseBinaryUploadsOptions) => {
  const [isRunning, setIsRunning] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const mountedRef = useRef(true);

  useEffect(() => {
    mountedRef.current = true;
    return () => {
      mountedRef.current = false;
    };
  }, []);

  const resolvedEnvironment = useMemo(
    () => resolveEnvironment(options.environment),
    [options.environment]
  );
  const { keypair } = options;

  const createUploadCredentials = useCallback(
    async (payload: {
      reference: string;
      fileName: string;
      fileType: string;
    }) => {
      if (!keypair) throw new Error("No keypair available");

      return WelshareApi.fetchS3WriteDelegation(
        keypair,
        payload,
        resolvedEnvironment
      );
    },
    [keypair, resolvedEnvironment]
  );

  const downloadAndDecryptFile = useCallback(
    async (documentId: string): Promise<File | undefined> => {
      if (!keypair) throw new Error("No keypair available");

      try {
        setIsRunning(true);
        setError(null);

        const { binaryFile, data } = await WelshareApi.fetchBinaryData(
          keypair,
          resolvedEnvironment,
          documentId
        );

        const encryptionKey: EncryptionKey = JSON.parse(
          binaryFile.encryption_key
        );
        const decryptedData = await decrypt(await data, encryptionKey);

        if (!decryptedData) throw new Error("Failed to decrypt file");

        return new File([decryptedData], binaryFile.file_name, {
          type: binaryFile.file_type,
        });
      } catch (err) {
        const errorMessage =
          err instanceof Error ? err.message : "Failed to download/decrypt";
        if (mountedRef.current) setError(errorMessage);
        return undefined;
      } finally {
        if (mountedRef.current) setIsRunning(false);
      }
    },
    [keypair, resolvedEnvironment]
  );

  return { createUploadCredentials, downloadAndDecryptFile, isRunning, error };
};

Usage:

const { createUploadCredentials, downloadAndDecryptFile } = useBinaryUploads({
  keypair: storageKeyPair,
  environment: "production",
});

// Get presigned URL for upload
const { presignedUrl, uploadKey } = await createUploadCredentials({
  reference: "questionnaire/abc/photo",
  fileName: file.name,
  fileType: file.type,
});

// Download and decrypt a file
const decryptedFile = await downloadAndDecryptFile(documentId);

Schemas

Fhir Resources

as an example, we're storing a subset of Fhir HL7 R5 QuestionnaireResponses. See questionnaire-response.schema.json.

  • A good json schema validation tester: https://www.jsonschemavalidator.net/
  • more tools https://forgetoolbox.com/
  • even more tools https://www.jsonforge.com/