F POLARIS · FHIR
sdk · getting started

Install and first steps.

@polaris/sdk is the unified SDK entry point for MIRA consumers. It provides @polaris/sdk/fhir for the FHIR client and @polaris/sdk/llm for the LLM gateway types. Both live on the private Verdaccio registry at npm.cognovis.de.

Step 1

Configure the registry.

The @polaris scope is served from https://npm.cognovis.de. Add the scope mapping to your project before installing.

Option A — .npmrc

@polaris:registry=https://npm.cognovis.de

Create or edit .npmrc at the project root. Applies to npm, pnpm, and Bun.

Option B — bunfig.toml

[install.scopes]
"@polaris" = "https://npm.cognovis.de"

Bun-native configuration in bunfig.toml. Scoped to this project directory.

Registry access

The registry at npm.cognovis.de is read-only and available anonymously for authorized consumers. If you receive a 401 Unauthorized error, see the Troubleshooting section for registry auth configuration.

Step 2

Install the package.

Install @polaris/sdk as the recommended entry point. It provides both the FHIR client (@polaris/sdk/fhir) and the LLM gateway types (@polaris/sdk/llm) as subpath exports. Pin to an exact version — the package is in v0.x, minor versions may carry breaking changes.

Bun

bun add @polaris/sdk@0.1.0

npm

npm install @polaris/sdk@0.1.0

pnpm

pnpm add @polaris/sdk@0.1.0

Low-level access: @polaris/fhir-de

@polaris/fhir-de remains published and available for consumers that need direct access to resource builders, generated profile classes, or FHIR extension constants. For most MIRA integrations, @polaris/sdk is the preferred entry point.

bun add @polaris/fhir-de@0.2.0  # FHIR-specific / low-level access

Builders vs. client API

For resource builders (buildPatient, buildEncounter, etc.), use @polaris/fhir-de directly. @polaris/sdk/fhir exposes the typed FHIR client API — createFhirDeClient, transport SPI, error types, and extractNextPageUrl. Both packages are available in the same project.

Step 3

Use the FHIR client.

Import createFhirDeClient from @polaris/sdk/fhir to build a typed FHIR client against your Aidbox instance.

import { createFhirDeClient } from '@polaris/sdk/fhir'
import { FPDEPatientProfile } from '@polaris/fhir-de'

const client = createFhirDeClient({
  baseUrl: 'https://aidbox.example.com/fhir',
  transport: myFetchTransport,
})

// Profile-scoped typed CRUD
const patientClient = client.forProfile(FPDEPatientProfile, 'Patient')
const patient = await patientClient.read('pat-12345')

LLM gateway types

Import PII-safe prompt constructors, capability routing, and PII scanning from @polaris/sdk/llm. Server-side symbols (createApp, loadCatalogFromFile) are intentionally excluded — the /llm subpath is safe to bundle in browser and edge runtimes.

import { AnonymizedPrompt, routeRequest } from '@polaris/sdk/llm'

const prompt = AnonymizedPrompt.from({
  anonymizedText: 'Diagnose: Hypertonie',
})

Optional piiMode

createFhirDeClient accepts an optional piiMode field. The default is 'real' (no post-processing). With piiMode: 'anonymized', PII fields (name, identifier, address, telecom, photo) are stripped from FHIR responses. birthDate is NOT stripped. Requires the POLARIS_ANON_SALT environment variable.

const client = createFhirDeClient({
  baseUrl,
  transport,
  piiMode: 'anonymized',
})

Step 4

Combine FHIR + LLM in one flow.

The two SDK halves compose naturally: read a patient via @polaris/sdk/fhir with piiMode: 'anonymized', then feed the anonymized data into an AnonymizedPrompt from @polaris/sdk/llm for LLM routing. This example uses createMockFhirTransport so no live Aidbox is required.

import {
  createFhirDeClient,
  createMockFhirTransport,
} from '@polaris/sdk/fhir'
import { AnonymizedPrompt } from '@polaris/sdk/llm'
import { FPDEPatientProfile } from '@polaris/fhir-de'

// 1. Set up an in-memory mock transport (no Aidbox needed for local dev)
const mock = createMockFhirTransport({
  fixtures: [{
    resourceType: 'Patient',
    id: 'pat-001',
    meta: { profile: ['https://fhir.cognovis.de/praxis/StructureDefinition/fpde-patient'] },
    name: [{ family: 'Mustermann', given: ['Max'] }],
    identifier: [{ value: '123456789' }],
    address: [{ city: 'Berlin' }],
    telecom: [{ value: '+49 30 12345' }],
    birthDate: '1975-03-22',
    gender: 'male',
  }],
})

// 2. Build the FHIR client with PII anonymization enabled
//    Requires POLARIS_ANON_SALT to be set in the environment
//    In production, generate with: crypto.randomBytes(32).toString('hex')
process.env.POLARIS_ANON_SALT = 'demo-salt-replace-in-production-use-32-plus-random-bytes'

const client = createFhirDeClient({
  baseUrl: 'http://mock',
  transport: mock,
  piiMode: 'anonymized',  // name, identifier, address, telecom, photo stripped from responses
})

// 3. Read patient — PII is stripped before reaching this code
const patients = client.forProfile(FPDEPatientProfile, 'Patient')
const patient = await patients.read('pat-001')

// patient.name === undefined (stripped)
// patient.identifier === undefined (stripped)
// patient.address === undefined (stripped)
// patient.telecom === undefined (stripped)
// patient.birthDate === '1975-03-22' (NOT stripped — birthDate is preserved)
// patient.id === 'anon-3f8a2b1c94d7' (SHA-256 hash of salt:pat-001, first 12 hex chars)
// patient.gender === 'male' (non-PII, preserved)

// 4. Build an AnonymizedPrompt from the de-identified data
//    This is the PII-safe input for LLM routing
const prompt = AnonymizedPrompt.from({
  anonymizedText: [
    `Patient (ID: ${patient?.id ?? 'unknown'}, gender: ${patient?.gender ?? 'unknown'})`,
    'Chief complaint: persistent back pain for 3 weeks, no radiation.',
    'Please suggest relevant ICD-10 codes.',
  ].join(' '),
})

// 5. Route to the LLM gateway (gateway not configured here — shows integration point)
//    In a real MIRA integration, pass prompt.text to the gateway:
//
//    const response = await app.fetch(new Request('http://localhost/v1/chat/completions', {
//      method: 'POST',
//      headers: { 'Content-Type': 'application/json' },
//      body: JSON.stringify({
//        messages: [{ role: 'user', content: prompt.text }],
//        gateway: {
//          requires: ['text', 'medicalCoding', 'germanLanguage'],
//          intent: 'billing-coding-suggest',
//          pii: 'anonymized',
//        },
//      }),
//    }))
//
// The AnonymizedPrompt type enforces at compile time that no RealPrompt reaches
// an intent declared pii: 'anonymized' — the TypeScript compiler rejects it.

console.log('Prompt ready for LLM routing:', prompt.text.slice(0, 80) + '...')

FHIR side

piiMode: 'anonymized' on createFhirDeClient wraps the transport in AnonymizingTransport. Resource IDs are hashed, name / identifier / address / telecom / photo removed. birthDate is NOT stripped.

Boundary

AnonymizedPrompt.from() is the compile-time boundary between PII-containing and PII-free data. It can only be constructed from AnonymizedInput — no raw patient strings slip through.

LLM side

prompt.text is safe to send to any intent with pii: 'anonymized'. The gateway validates PII mode before dispatch and writes an audit entry.