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

gdc-sdk-client-ts

v1.0.4

Published

This SDK provides a platform-agnostic, secure interface for interacting with the backend services. It encapsulates the complexity of cryptographic operations, secure storage, and the two-phase authentication model required by the API.

Readme

Client SDK (client-sdk-ts)

This SDK provides a platform-agnostic, secure interface for interacting with the backend services. It encapsulates the complexity of cryptographic operations, secure storage, and the two-phase authentication model required by the API.

Core Architectural Concepts

Data Integrity & Conflict Resolution Model

The SDK uses a sophisticated model to ensure data integrity and manage versioning, especially in scenarios where a draft might be edited on multiple devices before being submitted.

  • job.id / job.content.jti (Stable Job/Draft ID):

    • Purpose: This is the persistent, primary identifier for the entire job or "draft."
    • Implementation: It's a UUIDv4, created once when the draft is first initiated.
    • Behavior: It never changes, even when the content is edited. It serves as the primary key in the local JobManager vault and is the public-facing identifier used for anti-replay protection, as required by FAPI.
  • job.versionId / job.content.id (Ephemeral Content Version ID):

    • Purpose: This is a verifiable "fingerprint" of the current version of the draft's content.
    • Implementation: It is a SHA hash of the canonicalized content object (with meta and id fields excluded).
    • Behavior: It is recalculated and changes every time the draft is saved. Its sole purpose is internal version tracking and integrity verification. Crucially, this id field within the payload is removed before the message is sent, as the digital signature covers the integrity of the sent content.
  • job.sequence (Ordering Timestamp):

    • Purpose: To provide a definitive order for different versions of a draft.
    • Implementation: An epoch timestamp (number) generated whenever a draft is saved.
    • Behavior: This is the primary mechanism for conflict resolution. The version with the highest sequence number is considered the most recent.
  • job.previousSequence (Version History Link):

    • Purpose: To create a linked list or "change history" of a draft's versions.
    • Implementation: When a draft is updated, the sequence number of the previous version is stored in this field.
    • Behavior: This allows the system to trace the history of edits. It is the key to detecting uncoordinated simultaneous edits. If a device tries to sync a version whose previousSequence does not match the sequence of the current latest version on the server, a conflict has occurred. The default strategy is last-write-wins (based on the highest sequence), but this history makes more complex merging strategies possible in the future.

User vs. Device Identity

1. Host vs. Gateway Providers

The SDK's data model and service resolution are built on a clear distinction between two entity types:

  • Host Provider: The central infrastructure provider. It is responsible for onboarding new organizations. Its DID Document contains registry services, such as createOrganization and confirmOrder. All onboarding operations are directed at the Host's DID.
  • Gateway Provider (Tenant): An individual organization that has been onboarded. Its DID Document contains entity and individual services for day-to-day operations (e.g., createEmployee, activateDevice). Once onboarded, an organization's services are resolved against its own DID.

2. The roleRegistry as the Single Source of Truth

The SDK is designed to be highly scalable and maintainable by centralizing all business logic for role capabilities into one place: client-sdk-ts/src/roleRegistry.ts.

  • roleRegistry.ts defines which services (e.g., PhysicianService) and capabilities (e.g., MedicationService) are available to a user based on their ISCO-08 role code.
  • capabilityMapper.ts is a pure, agnostic engine that reads this registry at runtime. It dynamically assembles and injects the correct services when a user session is created.
  • To add or modify a role's capabilities, a developer only needs to edit the roleRegistry.ts file. The rest of the system adapts automatically.

The SDK's security model is built on two distinct layers of identity and authentication, which are managed automatically.

Service-Layer Architecture: Hierarchical & Composable Capability Services

Fundamental Principle

The API must reflect a user's real-world capabilities, which are a combination of their role, their context, and their authorizations. The architecture is built on two core principles: Inheritance for "is-a" relationships (a Physician is-a Paramedic in terms of skills) and Composition for "has-a" capabilities (a Physician has-a medication administration capability).

Core Concepts

  • Context: The environment of the interaction: Organization or Family.
  • Role: The user's specific professional function, aligned with an ISCO-08 code (e.g., Physician, Paramedic).
  • Shared Capability: A function that multiple roles can perform, managed by a dedicated Capability Service (e.g., MedicationService).
  • Explicit Authorization Principle: Significant actions must be preceded by a signed, verifiable authorization object (e.g., a MedicationRequest signed as a JWS). The API does not allow for optional or implicit authorizations for such actions.

Key Components & Design Patterns

  1. Role Services (using Inheritance):

    • Represent the user's job in a logical inheritance chain that models skill sets. A more specialized role extends a more foundational one. This relationship is for code reuse and does not dictate the directory structure.
    • Example: PhysicianService (in health-care) can extend ParamedicService (in emergencies) to inherit emergency response capabilities.
  2. Capability Services (using Composition):

    • Specialized classes that manage a single, shared capability (e.g., MedicationService in src/services/capabilities). They are not part of the role inheritance chain.
    • Usage: Role services (PhysicianService, NurseService) hold an instance of the relevant capability service and delegate tasks to it.
    • Explicit Authorization: Methods in a capability service require a signed authorization object.
      • MedicationService.administer(patientDid: string, signedMedicationRequest: Jws)
  3. The Smart Hub (ProfileManager and capabilityMapper):

    • The capabilityMapper is the "brain". It receives the user's role and the entity's DidDocument.
    • It instantiates the final, most specific role service (e.g., new PhysicianService(...)).
    • It also instantiates and injects the required capability services (e.g., new MedicationService(...)) into the role service's constructor.
    • ProfileManager then exposes the final, fully-formed service object (e.g., session.professional.physician).

Example Directory & Class Structure

This architecture translates into the following physical directory structure. Note how the directory structure is organized by sector/domain, while class inheritance can cross these boundaries.

src/services/
├── base/
│   ├── BaseProfessionalService.ts
│   ├── BaseOrgAdminService.ts
│   └── BaseFamilyAdminService.ts
│
├── capabilities/
│   └── MedicationService.ts
│
├── org-admin/
│   └── OrgAdminService.ts
│
├── family-admin/
│   └── FamilyAdminService.ts
│
├── professional/
│   ├── health-care/
│   │   ├── PhysicianService.ts      // Extends ParamedicService
│   │   ├── NurseService.ts
│   │   └── PhysiotherapistService.ts
│   │
│   ├── emergencies/
│   │   └── ParamedicService.ts      // Extends BaseProfessionalService
│   │
│   ├── health-insurance/
│   │   └── InsuranceAgentService.ts
│   │
│   └── care/
│       └── CaregiverService.ts
│
└── individual/
    └── IndividualAccountService.ts

1. The User's Identity (via id_token)

  • What it is: A proof of who the human is. It's a standard OIDC id_token obtained from an external identity provider (e.g., Google, Apple, eIDAS).
  • When it's used: This token is used for initial user authentication and for "public" API calls that happen before a device profile is officially registered in the system (Pre-DCR).
  • Examples: registerOrganization, acceptLicenseOffer.
  • How the SDK uses it: The id_token is passed to "public" service methods (e.g., executePublicJob) to prove the legal representative's identity. It's also used during the acquisition of a smartToken.

2. The Device's Identity (via private_key_jwt Client Assertion)

  • What it is: A proof of what the client application/device is. The device's identity is its client_id, which is a did:web identifier created by the backend connector upon activation.
  • When it's used: It is used every time the SDK needs to request a smartToken from our own Authorization Server (/token endpoint). This happens for all secure, data-access calls after the device is registered (Post-DCR).
  • How it works: The SDK uses the device's private JWK (stored securely by the IWallet) to sign a JWT. This signed JWT is the "Client Assertion," and it proves to the token endpoint that the request is coming from a legitimate, registered client.

The Bridge: Acquiring a smartToken

For all Post-DCR operations, the SDK must acquire a short-lived smartToken (an OAuth 2.0 Access Token). To do this, our Authorization Server needs proof of both the device and the user.

  • Proof of Device: The private_key_jwt client assertion.
  • Proof of User: The user's current, active id_token.

The SmartTokenManager within the SDK handles this flow automatically. API service methods simply declare the scopes they need, and the manager orchestrates the acquisition and caching of the required smartToken.

Guiding Principles

  1. The API_INTEGRATORS_GUIDE.md is the Source of Truth: All API method implementations, service selectors, and payload structures must be based on the official documentation available at https://raw.githubusercontent.com/Global-DataCare/docs/refs/heads/main/API_INTEGRATORS_GUIDE.md. The SDK should not invent or assume logic.
  2. Test Data is the Second Source of Truth: The mock data files in client-sdk-ts/__tests__/data/ are sourced from the backend's own test suite and documentation. They represent the ground truth for API responses and must be used for all Jest tests and for the in-app DEMO_MODE.
  3. Specific Methods are Simple, The Engine is Smart: A specific API method (e.g., createOrganization) is responsible for knowing the "what" (the endpoint selector, the payload). The BaseApiService engine (resolveAndExecute) is responsible for the "how" (DID resolution, message construction, job submission).

End-to-End Onboarding Flow

The SDK is designed to follow a specific, multi-step business process for onboarding a new organization.

  1. orgAdmin.admin.createOrganization(): A legal representative, authenticated with an external OIDC id_token, submits the organization's details to the Host Provider's DID. The async response will contain an Offer.
  2. orgAdmin.admin.confirmOrder(): The representative accepts the offer by submitting an order, again to the Host Provider's DID. The async response will contain a set of license codes.
  3. Payment (External): If required, the user completes the payment flow.
  4. common.auth.activateDevice(): The representative (or another employee) uses a valid license code to register their device. This is a DCR (Dynamic Client Registration) call made to the Gateway's (Tenant's) DID. This call creates the client_id (did:web) for the device and marks it as active.
  5. smartToken Acquisition: From this point forward, all API calls are authorized (Post-DCR) and must acquire a smartToken. The SDK handles this automatically using the device's identity (via private_key_jwt) and the user's id_token.

Quick Start & Core Usage

This guide demonstrates how to implement the primary business flows using the SDK.

The Core Principle: The SDK is a Platform-Agnostic Engine

Think of the SDK as a powerful engine. It is designed to run in any environment but it cannot directly perform platform-specific tasks like storing a cryptographic key on a device. Instead, the SDK defines interfaces (or "ports") for these tasks, such as IWallet for secure storage or ICryptoHelper for cryptographic operations.

Your application is responsible for providing the concrete implementations (the "adapters" or "platform services") that connect to these ports.

For a detailed explanation of the overall multi-package architecture and how the different pieces (like adapters-sdk-expo and platformServices) fit together, please see the main project README.

Implementing the Platform Adapters (IWallet & IVaultRepository)

The two most important interfaces you must implement when bringing the SDK to a new platform are:

  1. IWallet: This interface defines the contract for secure cryptographic key storage. Your app must provide an object that implements this interface. For an Expo app, this is typically a class that uses expo-secure-store to interact with the device's Keychain or Keystore.

  2. IVaultRepository: This defines the contract for storing user data (like profiles and job requests). The SDK requires a factory function that can create a separate, isolated storage vault for each user profile.

Conceptual Example (platformServices.ts for a new platform):

// In a file like `platformServices.ts` in your new platform's source.

import { IWallet, IVaultRepository } from '@gdc/client-sdk'; // Assuming published package
import { MySecureStorage } from './my-platform-secure-storage';
import { MyDatabase } from './my-platform-database'; 

// 1. Your platform's implementation of IWallet
class MyPlatformWallet implements IWallet {
  // ... implement all methods of IWallet using your platform's secure storage ...
  async provisionKeys(entityId: string): Promise<JwkSet> {
    const privateKey = await MySecureStorage.generateAndStoreKey(entityId); 
    return getPublicKey(privateKey);
  }
  // ... other methods like digest, protectConfidentialData, etc.
}

// 2. Create a single, app-wide instance of your wallet.
export const appWallet = new MyPlatformWallet();

// 3. Your platform's factory function for creating user vaults.
export function createVaultForProfile(profileId: string): IVaultRepository {
  return new MyDatabase(profileId);
}

Using the Pre-built Expo Implementation

This project already provides the necessary platform-specific implementations for Expo. You do not need to build them from scratch.

  • Low-Level Adapters: The primitive adapters can be found in the adapters-sdk-expo/ directory.
  • High-Level Services: The concrete implementations are located in the platformServices/ directory.
    • platformServices/ExpoWallet.ts is the implementation of the IWallet interface.
    • platformServices/index.ts is where the singleton appWallet is instantiated and the createVaultForProfile function is defined.

Therefore, to use the pre-built services for Expo, you simply import them:

// In your ProfileContext.tsx or equivalent setup file:
import { appWallet, createVaultForProfile } from '../platformServices';
import { ClientSDK } from '../client-sdk-ts';

// ... then, when you initialize the SDK, you pass these directly.
const sdk = new ClientSDK(sdkConfig, appInfo, appWallet, verifierService, icaDid);

// ... and when you create a session, you pass the factory function.
await sdk.initializeSession(params, createVaultForProfile);

SDK Initialization (Platform-Specific)

The first step is to instantiate the ClientSDK. This requires providing the platform-specific "adapters" and implementations discussed above.

// In a central provider file, e.g., ProfileContext.tsx

import { ClientSDK, InitializeSessionParams, IscoCode } from '../client-sdk-ts';
import { appWallet, createVaultForProfile } from '../platformServices';
import { AdapterCryptoSdkExpo, AdapterNetworkSdkExpo, AdapterApiConfigSdkExpo } from '../adapters-sdk-expo';
import { VerifierService } from '../client-sdk-ts/src/VerifierService';
import { CryptographyService } from '../crypto-ts/CryptographyService';

// This initialization should happen once in your application's lifecycle.
const sdk = useMemo(() => {
    // 1. Instantiate platform-specific adapters.
    const cryptoAdapter = new AdapterCryptoSdkExpo();
    const sdkConfig = {
      crypto: cryptoAdapter,
      network: new AdapterNetworkSdkExpo(),
      api: new AdapterApiConfigSdkExpo(),
      fetcher: fetch.bind(window),
      // ... mockOptions for DEMO mode
    };

    // 2. Set up the Verifier Service with your trust anchors.
    const cryptoService = new CryptographyService(cryptoAdapter);
    const rootGoverningKeyPub = process.env.EXPO_PUBLIC_ROOT_GOVERNING_KEY_PUB; // From environment
    const verifierService = new VerifierService(cryptoService, rootGoverningKeyPub, sdkConfig.fetcher);
    
    // 3. Define your application's static info.
    const appInfo = { /* ... device info, app type, etc. ... */ };

    // 4. The trusted Intermediate CA DID.
    const icaDid = process.env.EXPO_PUBLIC_ICA_DID; // From environment

    // 5. Create the SDK instance, injecting your platform-specific wallet.
    return new ClientSDK(sdkConfig, appInfo, appWallet, verifierService, icaDid);
}, []);

Constructing the Provider DID

Before initializing a session, your application must determine the canonical DID of the provider (the organization or host) the user wants to connect to. The SDK supports two scenarios:

Case A: The organization has its own domain

If the user provides a dedicated domain for their organization (e.g., identity.acme.com), constructing the DID is straightforward.

Example:

const userInputDomain = 'identity.acme.com';
const providerDid = `did:web:${userInputDomain.toLowerCase()}`;

Case B: The organization is hosted by a provider

If the user's organization is hosted on a shared platform, you must construct a more complex "hosted" DID. The SDK provides a utility function for this.

Example:

import { buildHostedDidDetails } from '../client-sdk-ts';

const hostDomain = 'provider.example.com';
const tenantName = 'acme-health'; // Provided by the user in the UI

const { did: providerDid } = buildHostedDidDetails({
  host: hostDomain,
  alternateName: tenantName,
  jurisdiction: 'ES', // Or another value from the UI
});
// providerDid -> "did:web:provider.example.com:acme-health:cds-ES:v1:health-care"

Core Flow: Onboarding a New Organization

This flow uses the providerDid constructed in the previous step. It involves a human legal representative authenticating themselves to bootstrap the organization's identity and its first device.

// Assume you have the `providerDid` from the previous step.
// Assume you have the legal rep's idToken from an OIDC login.
const legalRepIdToken = '...'; 

// --- Step 1: Initialize a session for the Legal Representative ---
const session = await sdk.initializeSession({
  email: '[email protected]',
  role: `ISCO-08|${IscoCode.ManagingDirector}`,
  providerDid: providerDid, // The DID is the entry point
}, createVaultForProfile); // Pass the vault factory function here

const orgAdminService = session.orgAdmin.admin;

// --- Step 2: Create the Organization ---
// This call is directed at the HOST's DID.
const hostDid = 'did:web:provider.example.com';
const orgClaims = { /* ... organization registration data ... */ };
const { thid: createOrgThid } = await orgAdminService.createOrganization(orgClaims, hostDid, legalRepIdToken);
// Poll for the result, which will contain an Offer...

// --- Step 3: Confirm the Order ---
// The user accepts the offer. This call is also to the HOST's DID.
const offerId = '...'; // From the result of the previous step
const { thid: confirmOrderThid } = await orgAdminService.confirmOrder(offerId, hostDid, legalRepIdToken);
// Poll for the result, which will contain a license code...

// --- Step 4: Activate the First Device ---
// This call is directed at the new TENANT's DID (the one we used in initializeSession).
const licenseCode = '...'; // From the result of the previous step
const commonAuthService = session.common.auth;
const { thid: activateThid } = await commonAuthService.activateDevice(licenseCode, providerDid);
// Poll for the result. The device is now registered.

Note on Production vs. Test Onboarding

The self-service createOrganization flow detailed above is intended for the test-network. Onboarding a new organization in the production environment involves a manual verification process by the host provider to ensure the legitimacy of the legal entity.

Testing

Unit tests:

npm test

E2E (SDK against external gateway):

scripts/run-e2e-external.sh

Override the backend env file:

scripts/run-e2e-external.sh --env-file /path/to/.env

Core Flow: Day-to-Day Authenticated Operations

Once a device is registered, subsequent sessions are initialized using the same logic. The SDK handles token management automatically.

// Assume a registered employee logs in.
// The app first constructs the correct `providerDid` as shown above.

const providerDid = '...'; // Constructed using Case A or Case B.

// --- Step 1: Initialize Session ---
const session = await sdk.initializeSession({
  email: '[email protected]',
  role: `ISCO-08|${IscoCode.GeneralistMedicalPractitioner}`,
  providerDid: providerDid,
}, createVaultForProfile); // Pass the vault factory function

// --- Step 2: Perform an Authenticated Action ---
const physicianService = session.professional.physician;
if (physicianService) {
  // The SDK will automatically handle acquiring a smartToken for this call.
  const { thid } = await physicianService.createEmployee(...);
}

Other Business Flows

Flows for managing families, adding members, managing consent, and sending communications follow a similar pattern:

  1. Initialize a session for the authenticated user.
  2. Access the appropriate service from the session object (e.g., session.familyAdmin.admin).
  3. Call the method for the desired action.

For the specific service methods and the exact structure of the data payloads required, always consult the API_INTEGRATORS_GUIDE.md.