Skip to main content
Glama
export.ts4.13 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { SearchRequest } from '@medplum/core'; import { accepted, AccessPolicyInteraction, concatUrls, getResourceTypes, Operator, protectedResourceTypes, singularize, } from '@medplum/core'; import type { FhirRequest, FhirResponse } from '@medplum/fhir-router'; import type { Project, Resource, ResourceType } from '@medplum/fhirtypes'; import { getConfig } from '../../config/loader'; import { getAuthenticatedContext } from '../../context'; import { getPatientResourceTypes } from '../patient'; import { BulkExporter } from './utils/bulkexporter'; /** * Handles a bulk export request. * * Endpoint * [fhir base]/$export * * See: https://hl7.org/fhir/uv/bulkdata/export.html * See: https://hl7.org/fhir/R4/async.html * @param req - The FHIR request. * @returns The FHIR response. */ export async function bulkExportHandler(req: FhirRequest): Promise<FhirResponse> { return startExport(req, 'System'); } /** * Handles a Patient export request. * * Endpoint * [fhir base]/Patient/$export * * See: https://hl7.org/fhir/uv/bulkdata/export.html#endpoint---all-patients * See: https://hl7.org/fhir/R4/async.html * @param req - The FHIR request. * @returns The FHIR response. */ export async function patientExportHandler(req: FhirRequest): Promise<FhirResponse> { return startExport(req, 'Patient'); } async function startExport(req: FhirRequest, exportType: string): Promise<FhirResponse> { const ctx = getAuthenticatedContext(); const { baseUrl } = getConfig(); const since = singularize(req.query._since); const types = singularize(req.query._type)?.split(','); const exporter = new BulkExporter(ctx.repo); const bulkDataExport = await exporter.start(concatUrls(baseUrl, 'fhir/R4' + req.pathname)); exportResources(exporter, ctx.project, types, exportType, since) .then(() => ctx.logger.info('Export completed', { exportType, id: ctx.project.id })) .catch((err) => ctx.logger.error('Export failure', { exportType, id: ctx.project.id, error: err })); return [accepted(`${baseUrl}fhir/R4/bulkdata/export/${bulkDataExport.id}`)]; } export async function exportResources( exporter: BulkExporter, project: Project, types: string[] | undefined, exportLevel: string, since?: string ): Promise<void> { const resourceTypes = getResourceTypesByExportLevel(exportLevel); const pageSize = 1000; for (const resourceType of resourceTypes) { if ( !canBeExported(resourceType) || (types && !types.includes(resourceType)) || !exporter.repo.supportsInteraction(AccessPolicyInteraction.SEARCH, resourceType) ) { continue; } await exportResourceType(exporter, resourceType, pageSize, since); } // Close the exporter await exporter.close(project); } export async function exportResourceType<T extends Resource>( exporter: BulkExporter, resourceType: T['resourceType'], count: number, since?: string ): Promise<void> { const repo = exporter.repo; const searchRequest: SearchRequest<T> | undefined = { resourceType, count, filters: since ? [{ code: '_lastUpdated', operator: Operator.GREATER_THAN_OR_EQUALS, value: since }] : undefined, sortRules: [{ code: '_lastUpdated', descending: false }], }; await repo.processAllResources(searchRequest, async (resource) => { await exporter.writeResource(resource); }); // Close writer and free memory for this resource type immediately await exporter.closeWriter(resourceType); } function getResourceTypesByExportLevel(exportLevel: string): ResourceType[] { if (exportLevel === 'Patient') { return getPatientResourceTypes(); } return getResourceTypes(); } const unexportedResourceTypes = [ 'Binary', 'CodeSystem', 'SearchParameter', 'StructureDefinition', 'ValueSet', 'BulkDataExport', 'AsyncJob', 'AuditEvent', ]; function canBeExported(resourceType: string): boolean { return !unexportedResourceTypes.includes(resourceType) && !protectedResourceTypes.includes(resourceType); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server