"> Aller au contenu principal
TypeScript TypeScript v1.1.0 PolyForm NC 1.0

RPPS API TypeScript SDK

@qrcommunication/rppsapi

Node.js 18+, zero dependencies

1 Installation

npm
npm install @qrcommunication/rppsapi
pnpm
pnpm add @qrcommunication/rppsapi
yarn
yarn add @qrcommunication/rppsapi
Zero dépendances. Le SDK utilise uniquement l'API native fetch (disponible nativement depuis Node.js 18+). Aucune dépendance externe n'est requise.
ESM uniquement. Ce package est distribué en ECMAScript Modules ("type": "module"). Assurez-vous que votre projet est configuré pour utiliser ESM, ou utilisez un bundler compatible (Vite, webpack 5+, esbuild).

Prérequis

Environnement Version minimale
Node.js 18.0.0+
TypeScript 5.0+ (recommandé 5.7+)
Deno 1.30+ (via npm specifier)
Bun 1.0+

2 Quick Start

TypeScript main.ts
import { RppsClient } from '@qrcommunication/rppsapi';
import type { RppsFullProfile, RppsSearchResponse } from '@qrcommunication/rppsapi';

// Initialiser le client
// Sans fhirApiKey, la source FHIR est automatiquement désactivée
const client = new RppsClient({
  fhirApiKey: process.env.FHIR_API_KEY, // optionnel — obtenir sur portal.api.esante.gouv.fr
  timeout: 30_000,
});

// ─── Profil complet par numéro RPPS ───────────────────────────
const profil: RppsFullProfile = await client.getByRpps('10005173140');

console.log(
  profil.identite.civiliteExercice.libelle,
  profil.identite.prenomExercice,
  profil.identite.nomExercice,
);
// → "Docteur Jean DUPONT"

console.log('Profession :', profil.profession.libelle);
// → "Médecin"

console.log('Activités :');
for (const activite of profil.activites) {
  console.log(`  - ${activite.structure.raisonSociale}`);
  console.log(`    ${activite.structure.adresse.codePostal} ${activite.structure.adresse.libelleCommune}`);
}

console.log('Diplômes :');
for (const da of profil.diplomesEtAutorisations) {
  if (da.diplome) {
    console.log(`  - [Diplôme] ${da.diplome.diplome.libelle}`);
  }
  if (da.autorisation) {
    console.log(`  - [Autorisation] ${da.autorisation.disciplineAutorisation.libelle}`);
  }
}

console.log('Savoir-faire :');
for (const sf of profil.savoirFaire) {
  console.log(`  - ${sf.savoirFaire.libelle} (${sf.typeSavoirFaire.libelle})`);
}

console.log('Cartes CPS :');
for (const carte of profil.cartesCps) {
  console.log(`  - ${carte.typeCarte.libelle} n°${carte.numeroCarte}`);
}

console.log('Messageries MSSanté :');
for (const bal of profil.messageriesMssante) {
  console.log(`  - ${bal.adresseBal} (${bal.typeBal})`);
}

// Métadonnées
console.log(`Durée totale : ${profil.metadata.totalDurationMs}ms`);
for (const source of profil.metadata.sources) {
  const status = source.success
    ? `OK (${source.rowCount} lignes, ${source.durationMs}ms)`
    : `ERREUR : ${source.error}`;
  console.log(`  ${source.source}: ${status}`);
}

// ─── Recherche par nom / prénom / code postal ──────────────────
const resultats: RppsSearchResponse = await client.search(
  { nom: 'DUPONT', codePostal: '75' },
  { page: 1, pageSize: 20 },
);

console.log(`${resultats.total} résultats trouvés`);
for (const r of resultats.results) {
  console.log(`  ${r.civilite} ${r.prenomExercice} ${r.nomExercice} — ${r.profession.libelle}`);
  console.log(`    RPPS: ${r.rpps} | ${r.structure.codePostal} ${r.structure.libelleCommune}`);
}

3 Configuration — RppsClientOptions

TypeScript
import { RppsClient } from '@qrcommunication/rppsapi';

const client = new RppsClient({
  // URL de base de l'API FHIR Annuaire Santé (ANS)
  // Défaut : 'https://gateway.api.esante.gouv.fr/fhir/v2'
  fhirBaseUrl: 'https://gateway.api.esante.gouv.fr/fhir/v2',

  // Clé API FHIR (ESANTE-API-KEY)
  // Obtenir gratuitement sur https://portal.api.esante.gouv.fr
  // Sans cette clé, la source FHIR est automatiquement désactivée
  fhirApiKey: process.env.FHIR_API_KEY,

  // URL de base de l'API Tabular data.gouv.fr
  // Défaut : 'https://tabular-api.data.gouv.fr/api/resources'
  tabularBaseUrl: 'https://tabular-api.data.gouv.fr/api/resources',

  // Timeout par requête en millisecondes
  // Défaut : 30000 (30 secondes)
  timeout: 30_000,

  // Nombre max de lignes par requête tabular
  // Défaut : 100
  tabularPageSize: 100,

  // Désactiver certaines sources (utile pour réduire la latence)
  // Valeurs possibles : 'fhir' | 'personne-activite' | 'diplomes'
  //                   | 'savoir-faire' | 'carte-cps' | 'mssante'
  disabledSources: ['fhir', 'mssante'],

  // Headers HTTP supplémentaires envoyés avec chaque requête
  headers: {
    'X-App-Name': 'mon-application',
  },
});

Interface RppsClientOptions

Propriété Type Défaut Description
fhirBaseUrl string? 'https://gateway.api.esante.gouv.fr/fhir/v2' URL de base de l'API FHIR Annuaire Santé (ANS)
fhirApiKey string? '' Clé API FHIR (ESANTE-API-KEY). Sans cette clé, la source FHIR est automatiquement désactivée.
tabularBaseUrl string? 'https://tabular-api.data.gouv.fr/api/resources' URL de base de l'API Tabular data.gouv.fr
timeout number? 30000 Timeout en millisecondes par requête (AbortController)
tabularPageSize number? 100 Nombre de lignes par page pour les requêtes tabular
disabledSources SourceName[]? [] Liste des sources à désactiver. Voir type SourceName.
headers Record<string, string>? {} Headers HTTP supplémentaires ajoutés à chaque requête
Clé FHIR — Obtention gratuite.
  1. Créer un compte sur portal.api.esante.gouv.fr
  2. Créer une application
  3. S'abonner à API Annuaire Santé en libre accès
  4. Récupérer la clé ESANTE-API-KEY dans l'onglet Credentials
Sans cette clé, le SDK fonctionne normalement avec les 5 sources data.gouv.fr uniquement.

4 API Reference

async client.getByRpps(rpps)

Récupère le profil complet d'un professionnel de santé à partir de son numéro RPPS. Interroge toutes les sources activées en parallèle via Promise.all() et fusionne les résultats en un seul objet structuré.

Signature

getByRpps(rpps: string): Promise<RppsFullProfile>

Paramètres

Paramètre Type Description
rpps string Numéro RPPS du praticien (exactement 11 chiffres). Les espaces en début/fin sont ignorés.

Retour

Retourne une Promise<RppsFullProfile>. Lance une Error si le numéro RPPS est invalide (format incorrect).

Exemple

TypeScript
const profil = await client.getByRpps('10005173140');

// Identité
console.log(profil.identite.civiliteExercice.libelle); // "Docteur"
console.log(profil.identite.nomExercice);              // "DUPONT"
console.log(profil.identite.prenomExercice);           // "Jean"

// Profession
console.log(profil.profession.libelle);              // "Médecin"
console.log(profil.profession.code);                 // "10"
console.log(profil.profession.categorie.libelle);    // "Civil"

// Activités (structures d'exercice)
profil.activites.forEach((a) => {
  console.log(a.structure.raisonSociale);
  console.log(a.structure.adresse.codePostal, a.structure.adresse.libelleCommune);
  console.log(a.modeExercice.libelle);  // "Libéral"
});

// Diplômes et autorisations
profil.diplomesEtAutorisations.forEach((da) => {
  if (da.diplome) console.log(da.diplome.diplome.libelle);
  if (da.autorisation) console.log(da.autorisation.disciplineAutorisation.libelle);
});

// Savoir-faire
profil.savoirFaire.forEach((sf) => {
  console.log(sf.savoirFaire.libelle, '—', sf.typeSavoirFaire.libelle);
});

// Cartes CPS/CPF
profil.cartesCps.forEach((c) => {
  console.log(c.typeCarte.libelle, c.numeroCarte);
  console.log(`Valide du ${c.dateDebutValidite} au ${c.dateFinValidite}`);
});

// Messageries MSSanté
profil.messageriesMssante.forEach((m) => {
  console.log(m.adresseBal, m.typeBal);
  console.log('Dématérialisation :', m.dematerialisation);
});

// Données FHIR (si clé API fournie)
if (profil.fhir.practitioner) {
  console.log('FHIR id :', profil.fhir.practitioner.id);
  console.log('PractitionerRoles :', profil.fhir.practitionerRoles.length);
}

// Métadonnées de la requête
console.log(`Interrogé le : ${profil.metadata.queriedAt}`);
console.log(`Durée totale : ${profil.metadata.totalDurationMs}ms`);

Recherche des professionnels de santé par nom, prénom et/ou code postal. La recherche est partielle et insensible à la casse (opérateur contains). Au moins un critère est obligatoire.

Signature

search(
  criteria: RppsSearchCriteria,
  options?: { page?: number; pageSize?: number }
): Promise<RppsSearchResponse>

Paramètres

Paramètre Type Description
criteria.nom string? Nom d'exercice (recherche partielle, insensible à la casse)
criteria.prenom string? Prénom d'exercice (recherche partielle, insensible à la casse)
criteria.codePostal string? Code postal (recherche exacte ou partielle — ex: '75' pour Paris)
options.page number? Page à récupérer. Défaut : 1
options.pageSize number? Nombre de résultats par page. Défaut : valeur de tabularPageSize (100)

Exemple

TypeScript
// Recherche par nom + code postal (département)
const r1 = await client.search({ nom: 'DUPONT', codePostal: '75' });
console.log(`${r1.total} résultats, page ${r1.page}/${Math.ceil(r1.total / r1.pageSize)}`);

for (const r of r1.results) {
  console.log(`${r.civilite} ${r.prenomExercice} ${r.nomExercice}`);
  console.log(`  Profession : ${r.profession.libelle}`);
  console.log(`  Structure  : ${r.structure.raisonSociale}`);
  console.log(`  Adresse    : ${r.structure.codePostal} ${r.structure.libelleCommune}`);
  console.log(`  RPPS       : ${r.rpps}`);
}

// Pagination
const page2 = await client.search(
  { nom: 'DUPONT', codePostal: '75' },
  { page: 2, pageSize: 20 },
);

// Recherche par prénom seul
const r2 = await client.search({ prenom: 'Nicolas' }, { pageSize: 10 });

// Enchaîner : recherche → profil complet
if (r1.results.length > 0) {
  const profil = await client.getByRpps(r1.results[0].rpps);
  console.log(`Diplômes : ${profil.diplomesEtAutorisations.length}`);
}

Sources individuelles

Pour un usage avancé, chaque source peut être interrogée directement en important les fonctions individuelles. Ces fonctions sont les briques internes utilisées par RppsClient.getByRpps().

TypeScript
import {
  fetchFhirByRpps,
  fetchPersonneActivite,
  searchPersonneActivite,
  fetchDiplomes,
  fetchSavoirFaire,
  fetchCartesCps,
  fetchMssante,
} from '@qrcommunication/rppsapi';
Fonction Source Retour Description
fetchFhirByRpps(rpps, opts) API FHIR ANS Promise<FhirData> Practitioner + PractitionerRoles FHIR. Requiert une clé API dans les headers (ESANTE-API-KEY).
fetchPersonneActivite(rpps, opts) data.gouv.fr Tabular Promise<{ identite, identificationNationale, profession, activites }> Identité, profession et structures d'exercice.
searchPersonneActivite(criteria, opts) data.gouv.fr Tabular Promise<{ results, total }> Recherche par nom/prénom/code postal.
fetchDiplomes(rpps, opts) data.gouv.fr Tabular Promise<DiplomeEtAutorisation[]> Diplômes et autorisations d'exercice.
fetchSavoirFaire(rpps, opts) data.gouv.fr Tabular Promise<SavoirFaire[]> Savoir-faire et compétences spécifiques.
fetchCartesCps(rpps, opts) data.gouv.fr Tabular Promise<CarteCps[]> Cartes CPS et CPF (professionnelles de santé).
fetchMssante(rpps, opts) data.gouv.fr Tabular Promise<MessagerieMssante[]> Boîtes aux lettres de messagerie sécurisée MSSanté.

Exemple d'utilisation directe

TypeScript
import { fetchFhirByRpps, fetchDiplomes } from '@qrcommunication/rppsapi';

// Interroger uniquement la source FHIR
const fhirData = await fetchFhirByRpps('10005173140', {
  baseUrl: 'https://gateway.api.esante.gouv.fr/fhir/v2',
  timeout: 15_000,
  headers: {
    'ESANTE-API-KEY': process.env.FHIR_API_KEY ?? '',
  },
});

console.log(fhirData.practitioner?.id);
console.log(fhirData.practitionerRoles.length);

// Interroger uniquement les diplômes
const diplomes = await fetchDiplomes('10005173140', {
  baseUrl: 'https://tabular-api.data.gouv.fr/api/resources',
  timeout: 20_000,
  pageSize: 100,
  headers: {},
});

console.log(`${diplomes.length} diplôme(s)`);
diplomes.forEach((da) => {
  if (da.diplome) console.log(da.diplome.diplome.libelle);
});

validateRpps(rpps)

Valide et normalise un numéro RPPS. Le RPPS doit contenir exactement 11 chiffres. Les espaces en début et fin de chaîne sont supprimés. Lance une Error si le format est invalide.

TypeScript
import { validateRpps } from '@qrcommunication/rppsapi';

// Valide (retourne le RPPS nettoyé)
const rpps = validateRpps('10005173140');  // → '10005173140'
const rppsWithSpaces = validateRpps('  10005173140  ');  // → '10005173140'

// Invalide (lance une Error)
try {
  validateRpps('123');  // Moins de 11 chiffres
} catch (error) {
  console.error(error.message);
  // → 'Numéro RPPS invalide : "123". Le RPPS doit contenir exactement 11 chiffres.'
}

// Utilisation pour pré-valider avant un appel API
function isValidRpps(rpps: string): boolean {
  try {
    validateRpps(rpps);
    return true;
  } catch {
    return false;
  }
}

5 Interfaces TypeScript

Toutes les interfaces sont exportées depuis le package principal et utilisables directement.

TypeScript Import des types
import type {
  RppsFullProfile,
  RppsClientOptions,
  RppsSearchCriteria,
  RppsSearchResponse,
  RppsSearchResult,
  Identite,
  Profession,
  Activite,
  Structure,
  AdresseStructure,
  DiplomeEtAutorisation,
  SavoirFaire,
  CarteCps,
  MessagerieMssante,
  FhirData,
  CodeLabel,
  SourceStatus,
  SourceName,
} from '@qrcommunication/rppsapi';

RppsFullProfile

Profil complet d'un professionnel de santé. Retourné par getByRpps().

interface RppsFullProfile {
  rpps: string;                              // Numéro RPPS (11 chiffres)
  identificationNationale: string;           // Identification nationale PP (préfixe type + RPPS)
  identite: Identite;                        // Identité du praticien
  profession: Profession;                    // Profession principale
  activites: Activite[];                     // Structures d'exercice
  diplomesEtAutorisations: DiplomeEtAutorisation[]; // Diplômes et autorisations
  savoirFaire: SavoirFaire[];               // Savoir-faire et compétences
  cartesCps: CarteCps[];                     // Cartes CPS/CPF
  messageriesMssante: MessagerieMssante[];   // Messageries MSSanté (BAL personnelles)
  fhir: FhirData;                            // Données FHIR brutes (usage avancé)
  metadata: {
    sources: SourceStatus[];                 // Statut de chaque source interrogée
    queriedAt: string;                       // ISO 8601 — date/heure de la requête
    totalDurationMs: number;                 // Durée totale en millisecondes
  };
}

Identite

interface Identite {
  civilite: CodeLabel;          // Civilité administrative (ex: { code: 'M', libelle: 'M.' })
  civiliteExercice: CodeLabel;  // Civilité d'exercice (ex: { code: 'DR', libelle: 'Docteur' })
  nomExercice: string;          // Nom de famille d'exercice
  prenomExercice: string;       // Prénom d'exercice
}

Profession

interface Profession {
  code: string;          // Code profession (ex: '10' = Médecin)
  libelle: string;       // Libellé profession (ex: 'Médecin')
  categorie: CodeLabel;  // Catégorie professionnelle (ex: { code: 'C', libelle: 'Civil' })
}

Activite

interface Activite {
  modeExercice: CodeLabel;           // Mode d'exercice (ex: 'Libéral', 'Salarié')
  savoirFaire: CodeLabel;            // Savoir-faire principal de l'activité
  typeSavoirFaire: CodeLabel;        // Type de savoir-faire
  secteurActivite: CodeLabel;        // Secteur d'activité (ex: 'Cabinet libéral')
  sectionTableauPharmaciens: CodeLabel; // Section tableau pharmaciens (si applicable)
  role: CodeLabel;                   // Rôle dans la structure
  genreActivite: CodeLabel;          // Genre d'activité
  autoriteEnregistrement: string;    // Autorité d'enregistrement
  structure: Structure;              // Structure d'exercice associée
}

Structure

interface Structure {
  identifiantTechnique: string;                       // Identifiant technique
  raisonSociale: string;                              // Raison sociale du site
  enseigneCommerciale: string;                        // Enseigne commerciale
  numeroSiret: string;                                // Numéro SIRET
  numeroSiren: string;                                // Numéro SIREN
  numeroFinessSite: string;                           // N° FINESS du site
  numeroFinessEtablissementJuridique: string;         // N° FINESS établissement juridique
  telephone: string;
  telephone2: string;
  telecopie: string;
  email: string;
  codeDepartement: string;
  libelleDepartement: string;
  ancienIdentifiant: string;
  adresse: AdresseStructure;
}

AdresseStructure

interface AdresseStructure {
  complementDestinataire: string;
  complementPointGeographique: string;
  numeroVoie: string;
  indiceRepetitionVoie: string;
  codeTypeVoie: string;
  libelleTypeVoie: string;
  libelleVoie: string;
  mentionDistribution: string;
  bureauCedex: string;
  codePostal: string;
  codeCommune: string;
  libelleCommune: string;
  codePays: string;
  libellePays: string;
}

DiplomeEtAutorisation

interface DiplomeEtAutorisation {
  diplome: {
    typeDiplome: CodeLabel;  // Type de diplôme (ex: 'Doctorat d'État de médecine')
    diplome: CodeLabel;      // Diplôme (code + libellé)
  } | null;
  autorisation: {
    typeAutorisation: CodeLabel;        // Type d'autorisation
    disciplineAutorisation: CodeLabel;  // Discipline concernée
  } | null;
}

SavoirFaire

interface SavoirFaire {
  profession: CodeLabel;               // Profession associée
  categorieProfessionnelle: CodeLabel; // Catégorie professionnelle
  typeSavoirFaire: CodeLabel;          // Type (ex: 'Spécialité ordinale', 'DESC')
  savoirFaire: CodeLabel;              // Savoir-faire (ex: 'Cardiologie')
}

CarteCps

interface CarteCps {
  typeCarte: CodeLabel;              // Type de carte (CPS, CPF, CPE...)
  numeroCarte: string;               // Numéro de la carte
  identifiantNationalCarte: string;  // Identifiant national
  dateDebutValidite: string;         // Date de début de validité (format AAAA-MM-JJ)
  dateFinValidite: string;           // Date de fin de validité (format AAAA-MM-JJ)
  dateOpposition: string;            // Date d'opposition (si applicable)
  dateMiseAJour: string;             // Date de dernière mise à jour
}

MessagerieMssante

interface MessagerieMssante {
  typeBal: string;             // Type de boîte aux lettres ('Personnelle', 'Organisationnelle')
  adresseBal: string;          // Adresse e-mail MSSanté (ex: jean.dupont@medecin.mssante.fr)
  dematerialisation: boolean;  // Indique si la BAL accepte la dématérialisation
  savoirFaire: CodeLabel;      // Savoir-faire associé
  structure: {
    identifiant: string;
    typeIdentifiant: string;
    serviceRattachement: string;
    raisonSociale: string;
    enseigneCommerciale: string;
    adresse: {
      complementLocalisation: string;
      complementDistribution: string;
      numeroVoie: string;
      complementNumeroVoie: string;
      typeVoie: string;
      libelleVoie: string;
      lieuDit: string;
      ligneAcheminement: string;
      codePostal: string;
      departement: string;
      pays: string;
    };
  };
}

FhirData

interface FhirData {
  practitioner: FhirPractitioner | null;    // Ressource FHIR Practitioner (null si non trouvé)
  practitionerRoles: FhirPractitionerRole[]; // Ressources FHIR PractitionerRole associées
}

CodeLabel

// Type de base omniprésent dans les données RPPS
interface CodeLabel {
  code: string;    // Code court (ex: '10', 'M', 'C')
  libelle: string; // Libellé complet (ex: 'Médecin', 'M.', 'Civil')
}

SourceStatus

interface SourceStatus {
  source: SourceName;    // Identifiant de la source interrogée
  success: boolean;      // true si la requête a réussi
  rowCount: number;      // Nombre de lignes/ressources retournées
  durationMs: number;    // Durée de la requête en millisecondes
  error?: string;        // Message d'erreur si success === false (ou 'disabled')
}

// Valeurs possibles pour SourceName :
type SourceName =
  | 'fhir'
  | 'personne-activite'
  | 'diplomes'
  | 'savoir-faire'
  | 'carte-cps'
  | 'mssante';

RppsSearchCriteria

// Au moins un champ obligatoire
interface RppsSearchCriteria {
  nom?: string;        // Nom d'exercice (recherche partielle, insensible à la casse)
  prenom?: string;     // Prénom d'exercice (recherche partielle, insensible à la casse)
  codePostal?: string; // Code postal (ex: '75' pour Paris, '69001' pour Lyon 1er)
}

RppsSearchResponse

interface RppsSearchResponse {
  results: RppsSearchResult[]; // Résultats de la page courante
  total: number;               // Nombre total de résultats correspondants
  page: number;                // Numéro de la page courante
  pageSize: number;            // Taille de la page
  durationMs: number;          // Durée de la requête en millisecondes
  criteria: RppsSearchCriteria; // Critères utilisés (écho)
}

RppsSearchResult

interface RppsSearchResult {
  rpps: string;
  identificationNationale: string;
  civilite: string;
  nomExercice: string;
  prenomExercice: string;
  profession: CodeLabel;
  categorieProfessionnelle: CodeLabel;
  modeExercice: CodeLabel;
  savoirFaire: CodeLabel;
  structure: {
    raisonSociale: string;
    codePostal: string;
    libelleCommune: string;
    departement: string;
  };
}

6 Sources de données

Le SDK agrège 6 sources publiques en parallèle pour construire le profil complet.

Source API Données fournies Clé API requise
personne-activite data.gouv.fr Tabular API Identité, profession, structures d'exercice, adresses Non
diplomes data.gouv.fr Tabular API Diplômes d'État, autorisations d'exercice Non
savoir-faire data.gouv.fr Tabular API Spécialités, DESC, savoir-faire ordinaux Non
carte-cps data.gouv.fr Tabular API Cartes CPS, CPF, CPE (professionnelles de santé) Non
mssante data.gouv.fr Tabular API Boîtes aux lettres de messagerie sécurisée MSSanté Non
fhir API FHIR Annuaire Santé (ANS) Ressources FHIR R4 : Practitioner + PractitionerRole Oui (ESANTE-API-KEY)
Comportement en cas d'échec partiel. Si une ou plusieurs sources échouent (erreur réseau, timeout, API indisponible), le SDK retourne quand même un RppsFullProfile avec les données disponibles. Les sources en erreur ont success: false dans metadata.sources. Aucune exception n'est levée pour les erreurs partielles.

7 Différences vs SDK PHP

Aspect SDK TypeScript/JS SDK PHP
Package @qrcommunication/rppsapi qrcommunication/rppsapi
Runtime Node.js 18+ / Bun / Deno PHP 8.1+
Format de module ESM uniquement ("type": "module") PSR-4 Composer
Dépendances Zéro dépendance — fetch natif guzzlehttp/guzzle ^7.0
Parallélisme Promise.all() — requêtes simultanées natives Requêtes concurrentes Guzzle (pool)
Timeout En millisecondes (timeout: 30_000) En secondes (timeout: 30.0)
Critères de recherche Objet littéral { nom, prenom, codePostal } Classe DTO RppsSearchCriteria
Exceptions Error native JavaScript RppsException (classe personnalisée)
Exportation des types Interfaces TypeScript exportées DTOs PHP avec propriétés typées
Sources individuelles Fonctions exportées directement Méthodes de classe (usage interne)

Équivalences de configuration

TypeScript
// TypeScript
const client = new RppsClient({
  fhirApiKey: process.env.FHIR_API_KEY,
  timeout: 30_000,   // millisecondes
  disabledSources: ['mssante'],
});
PHP
// PHP (équivalent)
$client = new RppsClient(new RppsClientOptions(
    fhirApiKey: $_ENV['FHIR_API_KEY'],
    timeout: 30.0,      // secondes
    disabledSources: ['mssante'],
));

8 Gestion des erreurs

Erreurs de validation

Les erreurs de validation (RPPS invalide, aucun critère de recherche) lancent une Error native JavaScript immédiatement, avant toute requête réseau.

TypeScript
try {
  const profil = await client.getByRpps('123'); // RPPS invalide
} catch (error) {
  if (error instanceof Error) {
    console.error(error.message);
    // → 'Numéro RPPS invalide : "123". Le RPPS doit contenir exactement 11 chiffres.'
  }
}

try {
  const resultats = await client.search({}); // Aucun critère
} catch (error) {
  if (error instanceof Error) {
    console.error(error.message);
    // → 'Au moins un critère de recherche est obligatoire (nom, prenom ou codePostal).'
  }
}

Erreurs partielles (sources)

Les erreurs réseau sur une source individuelle ne propagent pas d'exception. La source est marquée success: false dans les métadonnées, et les données correspondantes sont remplacées par des valeurs par défaut (tableaux vides, objets vides).

TypeScript
const profil = await client.getByRpps('10005173140');

// Vérifier l'état de chaque source
for (const source of profil.metadata.sources) {
  if (!source.success) {
    console.warn(
      `Source "${source.source}" indisponible : ${source.error}`
    );
    // Continuer quand même avec les données disponibles
  }
}

// Vérifier qu'une source critique a réussi
const fhirSource = profil.metadata.sources.find((s) => s.source === 'fhir');
if (fhirSource && !fhirSource.success && fhirSource.error !== 'disabled') {
  console.warn('FHIR indisponible — données partielles');
}

Timeout

TypeScript
// Configurer un timeout court pour les environnements sensibles
const client = new RppsClient({ timeout: 10_000 }); // 10 secondes

try {
  const profil = await client.getByRpps('10005173140');
} catch (error) {
  if (error instanceof Error && error.name === 'AbortError') {
    console.error('Requête annulée (timeout)');
  }
}

Erreurs HTTP

TypeScript
// Les erreurs HTTP (4xx, 5xx) sur une source individuelle sont capturées
// et stockées dans metadata.sources[n].error
// Format du message d'erreur : "HTTP 503: Service Unavailable — https://..."

const profil = await client.getByRpps('10005173140');
const mssanteStatus = profil.metadata.sources.find((s) => s.source === 'mssante');

if (mssanteStatus?.error?.startsWith('HTTP 5')) {
  console.warn('Serveur MSSanté temporairement indisponible');
}

9 Exemples avancés

Désactiver des sources pour accélérer la réponse

TypeScript
// Ne récupérer que l'identité et les activités (sources les plus rapides)
const clientRapide = new RppsClient({
  disabledSources: ['fhir', 'diplomes', 'savoir-faire', 'carte-cps', 'mssante'],
});

const profil = await clientRapide.getByRpps('10005173140');
// Seule la source personne-activite est interrogée → < 1 seconde

Profils en parallèle pour une liste de RPPS

TypeScript
const client = new RppsClient();

const rppsList = ['10005173140', '10003704284', '10006847543'];

// Interroger les 3 profils simultanément
const [profil1, profil2, profil3] = await Promise.all(
  rppsList.map((rpps) => client.getByRpps(rpps))
);

console.log(profil1.identite.nomExercice);
console.log(profil2.identite.nomExercice);
console.log(profil3.identite.nomExercice);

Parcourir toutes les pages de résultats

TypeScript
import { RppsClient } from '@qrcommunication/rppsapi';
import type { RppsSearchResult } from '@qrcommunication/rppsapi';

const client = new RppsClient();

async function fetchAllResults(nom: string): Promise {
  const pageSize = 100;
  let page = 1;
  const allResults: RppsSearchResult[] = [];

  while (true) {
    const response = await client.search({ nom }, { page, pageSize });
    allResults.push(...response.results);

    if (allResults.length >= response.total || response.results.length === 0) {
      break;
    }
    page++;
  }

  return allResults;
}

const tousLesDupont = await fetchAllResults('DUPONT');
console.log(`${tousLesDupont.length} médecins nommés DUPONT`);

Utilisation dans une API Express / Fastify

TypeScript routes/rpps.ts
import express from 'express';
import { RppsClient, validateRpps } from '@qrcommunication/rppsapi';

const router = express.Router();

// Instancier une seule fois (connexion réutilisée)
const client = new RppsClient({
  fhirApiKey: process.env.FHIR_API_KEY,
  timeout: 20_000,
});

// GET /api/rpps/:rpps
router.get('/:rpps', async (req, res) => {
  try {
    const rpps = validateRpps(req.params.rpps);
    const profil = await client.getByRpps(rpps);
    res.json(profil);
  } catch (error) {
    if (error instanceof Error && error.message.includes('invalide')) {
      res.status(400).json({ error: error.message });
    } else {
      res.status(500).json({ error: 'Erreur interne' });
    }
  }
});

// GET /api/rpps/search?nom=DUPONT&codePostal=75
router.get('/search', async (req, res) => {
  const { nom, prenom, codePostal, page = '1', pageSize = '20' } = req.query as Record;

  try {
    const resultats = await client.search(
      { nom, prenom, codePostal },
      { page: parseInt(page), pageSize: parseInt(pageSize) },
    );
    res.json(resultats);
  } catch (error) {
    if (error instanceof Error) {
      res.status(400).json({ error: error.message });
    } else {
      res.status(500).json({ error: 'Erreur interne' });
    }
  }
});

export default router;

Utilisation avec Deno

TypeScript main.ts (Deno)
import { RppsClient } from 'npm:@qrcommunication/rppsapi';

const client = new RppsClient({
  fhirApiKey: Deno.env.get('FHIR_API_KEY'),
});

const profil = await client.getByRpps('10005173140');
console.log(profil.identite.nomExercice);