API reference
Referencia completa de la superficie pública de emafhir. Cada export
listado abajo viene con su signature TypeScript real, qué hace y al menos
un ejemplo de uso.
Subpath emafhir (core)
Sección titulada «Subpath emafhir (core)»createEmaClient(config)
Sección titulada «createEmaClient(config)»Factory tenant-aware. Es el entry point recomendado en Wave 1+.
function createEmaClient(config: EmaFhirClientConfig): EmaFhirClient;
type EmaFhirClientConfig = { tenant: Tenant; supabase: SupabaseClient; disableMetaInjection?: boolean; logger?: EmaFhirLogger;};
type Tenant = { id: string; slug: string; country: CountryCode; aidbox: TenantAidboxConfig; igVersion?: string; flags?: Record<string, unknown>;};
type TenantAidboxConfig = { clientId: string; clientSecret: string; organizationId: string; baseUrl: string; tokenUrl?: string;};
type CountryCode = "CL" | "MX" | "PE" | "CO" | "BR" | "AR" | "CR";Uso:
import { createEmaClient } from 'emafhir';import { createClient as createSupabase } from '@supabase/supabase-js';
const fhir = createEmaClient({ tenant: { id: 'tenant-uuid', slug: 'acme-clinic', country: 'MX', aidbox: { clientId: 'client-id', clientSecret: 'secret-decrypted-upstream', organizationId: 'org-uuid', baseUrl: 'https://aidbox.example.com', }, igVersion: 'emaig@2026.05.1', }, supabase: createSupabase('https://...', 'key'),});
const patient = await fhir.create({ resourceType: 'Patient', name: [{ given: ['Juan'], family: 'Pérez' }],});// patient.meta.profile = ['https://emahealth.io/fhir/mx/StructureDefinition/Patient']// patient.meta.tag = [{ system: 'urn:ema:tenant', code: 'tenant-uuid' }, ...]Throws: Error si falta tenant, tenant.country, tenant.aidbox.* o
supabase.
createClient(options) (legacy)
Sección titulada «createClient(options) (legacy)»Cliente bajo nivel sin meta-injection. Soportado indefinidamente. Usar cuando no necesitás auto-inyección o multi-país.
function createClient(options: FhirClientOptions): FhirClient;
type FhirClientOptions = { baseUrl: string; auth?: AuthStrategy; defaultHeaders?: Record<string, string>; organizationId?: string;};Uso:
import { createClient, bearerAuth } from 'emafhir';
const fhir = createClient({ baseUrl: 'https://aidbox.example.com', auth: bearerAuth(userJwt), organizationId: 'org-123',});
const patient = await fhir.read('Patient', 'p1');Class EmaFhirClient extends FhirClient
Sección titulada «Class EmaFhirClient extends FhirClient»Hereda todos los métodos de FhirClient y agrega meta-injection
automática + $translate opcional. Tipo público que devuelve
createEmaClient.
create<T>(resource, options?)
Sección titulada «create<T>(resource, options?)»async create<T extends FhirResource>( resource: T, options?: CreateOptions): Promise<T>;
interface CreateOptions { translate?: string[]; // FHIRPath de campos a enriquecer con $translate}Inyecta meta.profile y meta.tag antes del POST. Si options.translate
está, ejecuta $translate en los fields indicados (append-only).
const obs = await fhir.create( { resourceType: 'Observation', status: 'final', code: { coding: [{ system: 'urn:local:lab', code: 'GLU' }] }, valueQuantity: { value: 5.5, unit: 'mg/dL' }, }, { translate: ['code'] });// obs.code.coding ahora contiene urn:local:lab + LOINC equivalenteThrows: FhirError si Aidbox rechaza (validación, conflicto, etc.).
update<T>(resource, options?)
Sección titulada «update<T>(resource, options?)»async update<T extends FhirResource>( resource: T, options?: UpdateOptions): Promise<T>;
interface UpdateOptions { translate?: string[];}Reemplaza un recurso (PUT). Requiere resource.id. Inyecta meta igual
que create.
read<K>(resourceType, id)
Sección titulada «read<K>(resourceType, id)»async read<K extends ResourceType>( resourceType: K, id: string): Promise<ResourceTypeMap[K]>;Type-safe: el retorno se infiere automáticamente.
const patient = await fhir.read('Patient', 'p1');// ^ Patient (inferido sin genérico explícito)Throws: FhirError con e.isNotFound === true si 404.
search<K>(resourceType, params?) y searchResources<K>(...)
Sección titulada «search<K>(resourceType, params?) y searchResources<K>(...)»async search<K extends ResourceType>( resourceType: K, params?: SearchParams): Promise<Bundle<ResourceTypeMap[K]>>;
async searchResources<K extends ResourceType>( resourceType: K, params?: SearchParams): Promise<ResourceTypeMap[K][]>;search retorna Bundle completo (con paginación y links).
searchResources extrae el array de recursos directamente.
transaction(bundle)
Sección titulada «transaction(bundle)»async transaction(bundle: Bundle): Promise<Bundle>;Ejecuta un Bundle de tipo transaction o batch. Inyecta meta en cada
entry con resource antes del POST.
translate<T>(resource, fields)
Sección titulada «translate<T>(resource, fields)»async translate<T extends FhirResource>( resource: T, fields: string[]): Promise<T>;Aplica $translate post-hoc a un recurso ya existente. Útil cuando no
se hizo en el create.
patch<K>(resourceType, id, patches)
Sección titulada «patch<K>(resourceType, id, patches)»async patch<K extends ResourceType>( resourceType: K, id: string, patches: JsonPatch[]): Promise<ResourceTypeMap[K]>;
interface JsonPatch { op: 'add' | 'remove' | 'replace' | 'move' | 'copy' | 'test'; path: string; value?: unknown; from?: string;}JSON Patch RFC 6902.
const updated = await fhir.patch('Patient', 'p1', [ { op: 'replace', path: '/name/0/given/0', value: 'Carlos' }, { op: 'add', path: '/birthDate', value: '1980-05-15' },]);delete(resourceType, id)
Sección titulada «delete(resourceType, id)»async delete(resourceType: ResourceType | string, id: string): Promise<void>;vread<K>(resourceType, id, versionId)
Sección titulada «vread<K>(resourceType, id, versionId)»async vread<K extends ResourceType>( resourceType: K, id: string, versionId: string): Promise<ResourceTypeMap[K]>;Lee una versión histórica específica.
operation<T>(path, body?, method?)
Sección titulada «operation<T>(path, body?, method?)»async operation<T>( path: string, body?: unknown, method: 'GET' | 'POST' = 'POST'): Promise<T>;Invoca operaciones FHIR genéricas ($expand, $validate, etc.).
const expansion = await fhir.operation<ValueSet>( '/ValueSet/$expand?url=...', undefined, 'GET');nextPage<T>(bundle)
Sección titulada «nextPage<T>(bundle)»async nextPage<T extends FhirResource>( bundle: Bundle<T>): Promise<Bundle<T> | null>;Sigue el link relation="next" del Bundle anterior. Retorna null si no
hay más páginas.
let bundle = await fhir.search('Patient', { _count: 50 });while (bundle) { for (const entry of bundle.entry ?? []) { process(entry.resource); } bundle = await fhir.nextPage(bundle);}Properties
Sección titulada «Properties»readonly tenant: Tenant;readonly profileResolver: ProfileResolver;Class FhirError extends Error
Sección titulada «Class FhirError extends Error»Error de cualquier operación HTTP del SDK.
class FhirError extends Error { status: number; statusText: string; operationOutcome?: OperationOutcome;
get isNotFound(): boolean; // status === 404 get isConflict(): boolean; // status === 409 get isValidationError(): boolean; // 400 o 422 get isUnauthorized(): boolean; // 401 o 403
get issues(): Array<{ severity: string; code: string; diagnostics?: string; }>;}Uso:
try { await fhir.read('Patient', 'bad-id');} catch (e) { if (e instanceof FhirError && e.isNotFound) { return null; // tratar 404 como ausencia } throw e;}Helpers
Sección titulada «Helpers»ref(type, id, display?)
Sección titulada «ref(type, id, display?)»function ref( type: ResourceType | (string & {}), id: string, display?: string): Reference;Construye un Reference FHIR.
import { ref } from 'emafhir';
const encounter = { resourceType: 'Encounter', subject: ref('Patient', 'p1', 'Juan Pérez'), participant: [{ individual: ref('Practitioner', 'pr1') }],};transaction()
Sección titulada «transaction()»function transaction(): TransactionBuilder;Builder fluent para Bundle de tipo transaction o batch.
import { transaction } from 'emafhir';
const bundle = transaction() .create(patient) .update(observation) .delete('Task', 'task-old') .createIfNotExists(allergy, 'patient=Patient/p1&code=peanut') .build();
await fhir.transaction(bundle);Class TransactionBuilder
Sección titulada «Class TransactionBuilder»class TransactionBuilder { batch(): this; // Cambia type a "batch" create(resource: FhirResource): this; // POST update(resource: FhirResource): this; // PUT (requiere id) delete(resourceType: string, id: string): this; // DELETE createIfNotExists(resource: FhirResource, search: string): this; // POST + If-None-Exist build(): Bundle;}Funciones de bajo nivel
Sección titulada «Funciones de bajo nivel»injectMeta(resource, tenant, profileResolver)
Sección titulada «injectMeta(resource, tenant, profileResolver)»async function injectMeta<R extends FhirResource>( resource: R, tenant: Tenant, profileResolver: ProfileResolver): Promise<R>;Inyecta meta.profile (canonical sin versión) + meta.tag
(urn:ema:tenant, urn:ema:ig-version). Idempotente y
no-mutativo (devuelve un objeto nuevo).
Normalmente se invoca por EmaFhirClient automáticamente; útil para
debugging o casos custom.
applyAutoTranslate(resource, fields, context)
Sección titulada «applyAutoTranslate(resource, fields, context)»async function applyAutoTranslate<R extends Record<string, unknown>>( resource: R, fields: string[], context: AutoTranslateContext): Promise<R>;
type AutoTranslateContext = { invokeTranslate: (params: { system: string; code: string }) => Promise<TranslateResult>; logger: EmaFhirLogger;};Append-only. Si una llamada a $translate falla, se loggea warning y la
operación procede.
Subpath emafhir/auth
Sección titulada «Subpath emafhir/auth»clientCredentials(options)
Sección titulada «clientCredentials(options)»function clientCredentials(options: ClientCredentialsOptions): ClientCredentialsAuth;
type ClientCredentialsOptions = { tokenUrl: string; clientId: string; clientSecret: string; scope?: string;};OAuth2 client_credentials. Cachea el token hasta su expiración (con 60s de margen).
import { clientCredentials, createClient } from 'emafhir';
const auth = clientCredentials({ tokenUrl: 'https://aidbox.example.com/auth/token', clientId: 'my-client', clientSecret: 'my-secret',});
const fhir = createClient({ baseUrl: '...', auth });bearerAuth(token)
Sección titulada «bearerAuth(token)»function bearerAuth(token: string): BearerAuth;
class BearerAuth implements AuthStrategy { getAccessToken(): Promise<string>; setToken(token: string): void;}Bearer token estático (con setter para refresh).
Interface AuthStrategy
Sección titulada «Interface AuthStrategy»interface AuthStrategy { getAccessToken(): Promise<string>;}Para implementar tu propia estrategia (mTLS, custom JWT, etc.).
Subpath emafhir/search
Sección titulada «Subpath emafhir/search»search()
Sección titulada «search()»function search(): SearchBuilder;Builder fluent para SearchParams.
import { search } from 'emafhir';
const params = search() .where('name', 'García') .whereExact('gender', 'female') .whereDate('birthdate', 'ge', '1990-01-01') .whereToken('identifier', 'urn:national:cl:run', '12345678-5') .include('Patient', 'general-practitioner') .sort('-_lastUpdated') .count(20) .build();
const bundle = await fhir.search('Patient', params);Class SearchBuilder
Sección titulada «Class SearchBuilder»class SearchBuilder { where(key: string, value: string | number | boolean): this; whereExact(key: string, value: string): this; whereContains(key: string, value: string): this; whereMissing(key: string, missing?: boolean): this; whereToken(key: string, system: string, code: string): this; whereDate( key: string, prefix: 'eq' | 'ne' | 'lt' | 'gt' | 'le' | 'ge' | 'sa' | 'eb' | 'ap', date: string ): this; whereReference(key: string, resourceType: string, id: string): this; include(resourceType: string, param: string): this; revinclude(resourceType: string, param: string): this; sort(field: string, descending?: boolean): this; count(n: number): this; offset(n: number): this; elements(...fields: string[]): this; summary(mode: 'true' | 'text' | 'data' | 'count'): this; build(): SearchParams;}Subpath emafhir/types
Sección titulada «Subpath emafhir/types»Re-exporta todos los tipos FHIR R4 + tipos EMA. No tiene runtime — solo types.
import type { // FHIR base FhirResource, Bundle, Reference, CodeableConcept, Coding, Identifier, Meta, Period, Quantity, ContactPoint, Address, HumanName, Extension, Annotation, Dosage, Money, Ratio, Narrative, BundleEntry, BundleLink,
// 35 resources Patient, Encounter, Condition, Observation, Procedure, AllergyIntolerance, MedicationRequest, Medication, DiagnosticReport, Specimen, DocumentReference, Communication, CarePlan, Task, Coverage, Flag, RelatedPerson, Invoice, ChargeItem, PaymentNotice, Appointment, Schedule, Slot, Organization, Location, Device, Practitioner, PractitionerRole, HealthcareService, Questionnaire, QuestionnaireResponse, ActivityDefinition, ServiceRequest, PlanDefinition,
// Type maps ResourceTypeMap, ResourceType,
// EMA CountryCode, Tenant, TenantAidboxConfig, EmaFhirClientConfig, EmaFhirLogger,} from 'emafhir/types';ResourceTypeMap
Sección titulada «ResourceTypeMap»type ResourceTypeMap = { Patient: Patient; Observation: Observation; Encounter: Encounter; // ... 35 entries};
type ResourceType = keyof ResourceTypeMap;Lo que permite que fhir.read('Patient', id) retorne Patient sin
genérico explícito.
Subpath emafhir/validators
Sección titulada «Subpath emafhir/validators»function validateRun(run: string): { valid: boolean; reason?: string; normalized?: string };function validateCurp(curp: string): { valid: boolean; reason?: string; normalized?: string };function validateRfc(rfc: string): { valid: boolean; reason?: string; type?: 'fisica' | 'moral'; normalized?: string };function validateDni(dni: string): { valid: boolean; reason?: string; normalized?: string };Sólo validan estructura + checksum. Nunca consultan registros oficiales (SII, RENAPO, SAT, RENIEC).
import { validateRun, validateCurp, validateRfc, validateDni,} from 'emafhir/validators';
validateRun('12.345.678-5'); // { valid: true, normalized: '12345678-5' }validateRun('12.345.678-K'); // { valid: false, normalized: null }validateCurp('GARC850315HDFLLR03'); // { valid: true, normalized: 'GARC850315HDFLLR03' }validateRfc('XAXX010101000'); // { valid: true, type: 'fisica', ... }validateDni('12345678'); // { valid: true, normalized: '12345678' }Subpath emafhir/sdc
Sección titulada «Subpath emafhir/sdc»Renderer headless de Questionnaires + helpers para QuestionnaireResponse.
Requiere react >= 18 como peer dep.
useSDCForm(options) (React hook)
Sección titulada «useSDCForm(options) (React hook)»function useSDCForm(options: UseSDCFormOptions): UseSDCFormReturn;
type UseSDCFormOptions = { questionnaire: Questionnaire; initialResponse?: QuestionnaireResponse; subject?: { reference?: string }; encounter?: { reference?: string }; autoSaveInterval?: number; // ms; 0 deshabilita onAutoSave?: (response: QuestionnaireResponse) => void;};
type UseSDCFormReturn = { values: Record<string, unknown>; // linkId → answer errors: Record<string, string>; resolvedItems: ResolvedItem[]; // items aplanados con enableWhen resuelto setValue: (linkId: string, value: unknown) => void; validate: () => boolean; submit: () => QuestionnaireResponse | null; reset: () => void; isDirty: boolean;};assembleQuestionnaire(questionnaire, resolver, options?)
Sección titulada «assembleQuestionnaire(questionnaire, resolver, options?)»async function assembleQuestionnaire( questionnaire: Questionnaire, resolver: (canonicalUrl: string) => Promise<Questionnaire | undefined>, options?: { maxDepth?: number }): Promise<Questionnaire>;Implementa la operación SDC $assemble — resuelve sub-questionnaires
referenciados por extension sdc-questionnaire-subQuestionnaire.
Otros helpers
Sección titulada «Otros helpers»function resolveItems(items: QuestionnaireItem[] | undefined): ResolvedItem[];function buildResponse( questionnaireUrl: string | undefined, items: ResolvedItem[], values: Record<string, unknown>, options?: { status?: string; subject?: Reference; encounter?: Reference }): QuestionnaireResponse;function validateForm(items: ResolvedItem[], values: Record<string, unknown>): ValidationError[];function isItemEnabled(item: ResolvedItem): boolean;Patterns recurrentes
Sección titulada «Patterns recurrentes»Manejo de errores
Sección titulada «Manejo de errores»import { FhirError } from 'emafhir';
try { const updated = await fhir.update(resource);} catch (e) { if (!(e instanceof FhirError)) throw e;
if (e.isConflict) { // Optimistic lock: re-read y retry const fresh = await fhir.read(resource.resourceType, resource.id!); return fhir.update({ ...fresh, ...updates }); }
if (e.isValidationError) { // Mostrar issues al usuario return { errors: e.issues }; }
throw e;}Paginación completa
Sección titulada «Paginación completa»const results: Patient[] = [];let bundle = await fhir.search('Patient', { _count: 50 });
while (bundle) { for (const entry of bundle.entry ?? []) { if (entry.resource) results.push(entry.resource); } bundle = await fhir.nextPage(bundle);}Búsqueda + populate de referencias
Sección titulada «Búsqueda + populate de referencias»const params = search() .where('subject', `Patient/${patientId}`) .include('Observation', 'performer') .build();
const bundle = await fhir.search('Observation', params);
const observations = bundle.entry ?.filter((e) => e.search?.mode !== 'include') .map((e) => e.resource) ?? [];
const performers = bundle.entry ?.filter((e) => e.search?.mode === 'include') .map((e) => e.resource) ?? [];