UNPKG

fhir-kit-client

Version:
1,266 lines (1,203 loc) 42.6 kB
const queryString = require('query-string'); const { authFromCapability, authFromWellKnown } = require('./smart'); const HttpClient = require('./http-client'); const ReferenceResolver = require('./reference-resolver'); const Pagination = require('./pagination'); const { createQueryString, validResourceType } = require('./utils'); const { FetchQueue } = require('./fetch-queue'); const { deprecatePaginationArgs, deprecateHeaders } = require('./deprecations'); const CapabilityTool = require('./capability-tool'); /** * @module fhir-kit-client */ class Client { /** * Create a FHIR client. * * For details on what requestOptions are available, see the node `request` * documentation at https://github.com/request/request * * @example * * // minimial example * const client = new FhirKitClient({ url: 'http://baseurl/fhir' }); * * // exhaustive example * const options = { * baseUrl: 'http://baseurl/fhir', * customHeaders: { * 'x-some-header': 'some-custom-value' * }, * requestOptions: { * cert: certFileContent, * key: keyFileContent, * ca: caFileContent * }, * bearerToken: 'eyJhbGci...dQssw5c', * requestSigner: (url, requestOptions) => { * const signed = aws4.sign({ * path: url, * service: 'healthlake', * region: 'us-west-2', * method: requestOptions.method * }); * Object.keys(signed.headers).forEach((key) => { * requestOptions.headers.set(key, signed[key]); * }); * } * }; * * const client = new Client(options); * * @param {Object} config Client configuration * @param {String} config.baseUrl ISS for FHIR server * @param {Object} [config.customHeaders] Optional custom headers to send with * each request * @param {Object} [config.requestOptions] Optional custom request options for * @param {Function} [config.requestSigner] Optional pass in a function to sign the request. * instantiating the HTTP connection * @param {String} [config.bearerToken] Optional bearerToken to use for each * request. * @throws An error will be thrown unless baseUrl is a non-empty string. */ constructor({ baseUrl, customHeaders, requestOptions, requestSigner, bearerToken } = {}) { this.httpClient = new HttpClient({ baseUrl, customHeaders, requestOptions, requestSigner }); if (bearerToken) { this.httpClient.bearerToken = bearerToken; } this.resolver = new ReferenceResolver(this); this.pagination = new Pagination(this.httpClient); } /** * Given a Client response, returns the underlying HTTP request and response * objects. * * @static * * @example * * const Client = require('fhir-kit-client'); * * fhirClient.read({ * resourceType: 'Patient', * id: 12345, * }).then((data) => { * const { response, request } = Client.httpFor(data); * console.log(response.status); * console.log(request.headers); * }); * * @param {Object} requestResponse to one of the FHIR Kit Client requests * @return {Object} object containing http request and response */ static httpFor(requestResponse) { return { request: HttpClient.requestFor(requestResponse), response: HttpClient.responseFor(requestResponse), }; } /** * Get the baseUrl. * * @return {String} - Get the baseUrl */ get baseUrl() { return this.httpClient && this.httpClient.baseUrl; } /** * Set the baseUrl. * * @param {String} url - Set the baseUrl * @throws An error will be thrown unless baseUrl is a non-empty string. */ set baseUrl(url) { if (this.httpClient) { this.httpClient.baseUrl = url; } } /** * Get custom headers. * * @return {Object} - Get the custom headers */ get customHeaders() { return this.httpClient.customHeaders; } /** * Set custom headers. * * @param {Object} headers - Set custom headers to be sent with each request */ set customHeaders(headers) { this.httpClient.customHeaders = headers; } /** * Set the Authorization header to "Bearer ${token}". * * @param {String} token The access token value. */ set bearerToken(token) { this.httpClient.bearerToken = token; } /** * Resolve a reference and return FHIR resource * * From: http://hl7.org/fhir/STU3/references.html, a reference can be: 1) * absolute URL, 2) relative URL or 3) an internal fragement. In the case of * (2), there are rules on resolving references that are in a FHIR bundle. * * @async * * @example * * // Always does a new http request * client.resolve({ reference: 'http://test.com/fhir/Patient/1' }).then((patient) => { * console.log(patient); * }); * * // Always does a new http request, using the client.baseUrl * client.resolve({ reference: 'Patient/1' }).then((patient) => { * console.log(patient); * }); * * // Try to resolve a patient in the bundle, otherwise build request * // at client.baseUrl * client.resolve({ reference: 'Patient/1', context: bundle }).then((patient) => { * console.log(patient); * }); * * // Resolve a patient contained in someResource (see: * // http://hl7.org/fhir/STU3/references.html#contained) * client.resolve({ reference: '#patient-1', context: someResource }).then((patient) => { * console.log(patient); * }); * * @param {Object} params - The request parameters. * @param {String} params.reference - FHIR reference * @param {Object} [params.context] - Optional bundle or FHIR resource * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resource */ resolve({ reference, context, headers, options = {} } = {}) { return this.resolver.resolve({ reference, context, options: deprecateHeaders(options, headers), }); } /** * Obtain the SMART OAuth URLs from the Capability Statement, or * any of the .well-known addresses. * * See: http://docs.smarthealthit.org/authorization/conformance-statement/ * * @async * * @example * * // Using promises * fhirClient.smartAuthMetadata().then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.smartAuthMetadata(); * console.log(response); * * @param {Object} [params] - The request parameters. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} contains the following SMART URL: authorizeUrl, * tokenUrl, registerUrl, manageUrl */ async smartAuthMetadata({ headers, options = {} } = {}) { const fetchOptions = { options: deprecateHeaders(options, headers) }; if (!(fetchOptions.options.headers)) { fetchOptions.options.headers = {}; } fetchOptions.options.headers.accept = 'application/fhir+json,application/json'; const normalizedBaseUrl = this.baseUrl.replace(/\/*$/, '/'); const queue = new FetchQueue(); const metadataJob = queue.buildJob(); const wellknownSmartJob = queue.buildJob(); const wellknownOidcJob = queue.buildJob(); const errors = []; return new Promise((resolve, reject) => { function handleError(error) { if (errors.push(error) === queue.numJobs) { reject(new Error(errors.map((e) => e.message).join('; '))); } } this.httpClient.request('GET', `${normalizedBaseUrl}.well-known/smart-configuration`, wellknownSmartJob.addSignalOption(fetchOptions)) .then((r) => { queue.safeAbortOthers(wellknownSmartJob); return resolve(authFromWellKnown(r)); }) .catch((e) => handleError(e)); this.capabilityStatement(metadataJob.addSignalOption(fetchOptions)) .then((r) => { queue.safeAbortOthers(metadataJob); return resolve(authFromCapability(r)); }) .catch((e) => handleError(e)); this.httpClient.request('GET', `${normalizedBaseUrl}.well-known/openid-configuration`, wellknownOidcJob.addSignalOption(fetchOptions)) .then((r) => { queue.safeAbortOthers(wellknownOidcJob); return resolve(authFromWellKnown(r)); }) .catch((e) => handleError(e)); }); } /** * Get the capability statement. * * @async * * @example * * // Using promises * fhirClient.capabilityStatement().then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.capabilityStatement(); * console.log(response); * * @param {Object} [params] - The request parameters. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} capability statement FHIR resource. */ capabilityStatement({ headers, options = {} } = {}) { if (!this.metadata) { this.metadata = this.httpClient.get('metadata', deprecateHeaders(options, headers)); } return this.metadata; } /** * Run a request. * * @example * * // Defaults to GET * fhirClient.request('Patient/123') * .then(data => console.log(data)); * * fhirClient.request('Patient/123', { method: 'DELETE'}) * .then(data => console.log(data)); * * fhirClient.request('Patient', { method: 'POST', body: myNewPatient }) * .then(data => console.log(data)); * * @param {String} requestUrl - URL, can be relative to base or absolute * @param {Object} params - (optional) Request params * @param {String} params.method - (optional) HTTP method (defaults to GET) * @param {Object} params.options - (optional) additional request options (e.g. headers) * @param {Object} params.body - (optional) request body * @return {Promise<Object>} Response */ request(requestUrl, { method = 'GET', options = {}, body } = {}) { if (options.method && options.method !== method) { /* eslint-disable-next-line no-console */ console.warn(`WARNING: 'options.method' has been specified: ${options.method} but will be ignored. Use 'method' instead.`); } return this.httpClient.request(method, requestUrl, options, body); } /** * Get a resource by FHIR id. * * @example * * // Using promises * fhirClient.read({ * resourceType: 'Patient', * id: 12345, * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.read({ resourceType: 'Patient', id: 12345 }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.resourceType - The resource type (e.g. "Patient", * "Observation"). * @param {String} params.id - The FHIR id for the resource. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resource */ read({ resourceType, id, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } return this.httpClient.get(`${resourceType}/${id}`, deprecateHeaders(options, headers)); } /** * Get a resource by id and version. * * @example * * // Using promises * fhirClient.vread({ * resourceType: 'Patient', * id: '12345', * version: '1', * }).then(data => console.log(data)); * * // Using async * let response = await fhirClient.vread({ * resourceType: 'Patient', * id: '12345', * version: '1', * }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.resourceType - The resource type (e.g. "Patient", * "Observation"). * @param {String} params.id - The FHIR id for the resource. * @param {String} params.version - The version id for the resource. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resource */ vread({ resourceType, id, version, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } return this.httpClient.get( `${resourceType}/${id}/_history/${version}`, deprecateHeaders(options, headers), ); } /** * Create a resource. * * @example * const newPatient = { * resourceType: 'Patient', * active: true, * name: [{ use: 'official', family: 'Coleman', given: ['Lisa', 'P.'] }], * gender: 'female', * birthDate: '1948-04-14', * } * * // Using promises * fhirClient.create({ * resourceType: 'Patient', * body: newPatient, * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.create({ * resourceType: 'Patient', * body: newPatient, * }) * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.resourceType - The FHIR resource type. * @param {String} params.body - The new resource data to create. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resource */ create({ resourceType, body, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } return this.httpClient.post(resourceType, body, deprecateHeaders(options, headers)); } /** * Delete a resource by FHIR id. * * @example * * // Using promises * fhirClient.delete({ * resourceType: 'Patient', * id: 12345, * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.delete({ resourceType: 'Patient', id: 12345 }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.resourceType - The resource type (e.g. "Patient", "Observation"). * @param {String} params.id - The FHIR id for the resource. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} Operation Outcome FHIR resource */ delete({ resourceType, id, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } return this.httpClient.delete(`${resourceType}/${id}`, deprecateHeaders(options, headers)); } /** * Update a resource by FHIR id. * * @example * * const updatedPatient = { * resourceType: 'Patient', * birthDate: '1948-04-14', * } * * // Using promises * fhirClient.update({ * resourceType: 'Patient', * id: 12345, * body: updatedPatient, * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.update({ * resourceType: 'Patient', * id: 12345, * body: updatedPatient, * }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.resourceType - The resource type (e.g. "Patient", * "Observation"). * @param {String} params.id - The FHIR id for the resource. * @param {String} params.searchParams - For a conditional update the searchParams are specified instead of the id, see https://www.hl7.org/fhir/http.html#cond-update * @param {String} params.body - The resource to be updated. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resource */ update({ resourceType, id, searchParams, body, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } if (id && searchParams) { throw new Error('Conditional update with search params cannot be with id', resourceType); } if (searchParams) { const searchQuery = createQueryString(searchParams); return this.httpClient.put( `${resourceType}?${searchQuery}`, body, deprecateHeaders(options, headers), ); } return this.httpClient.put( `${resourceType}/${id}`, body, deprecateHeaders(options, headers), ); } /** * Patch a resource by FHIR id. * * From http://hl7.org/fhir/STU3/http.html#patch: * Content-Type is 'application/json-patch+json' * Expects a JSON Patch document format, see http://jsonpatch.com/ * * @example * * // JSON Patch document format from http://jsonpatch.com/ * const JSONPatch = [{ op: 'replace', path: '/gender', value: 'male' }]; * * // Using promises * fhirClient.patch({ * resourceType: 'Patient', * id: 12345, * JSONPatch, * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.patch({ * resourceType: 'Patient', * id: 12345, * JSONPatch * }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.resourceType - The resource type (e.g. "Patient", * "Observation"). * @param {String} params.id - The FHIR id for the resource. * @param {Array} params.JSONPatch - A JSON Patch document containing an array * of patch operations, formatted according to http://jsonpatch.com/. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resource */ patch({ resourceType, id, JSONPatch, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } // Content-Type is 'application/json-patch+json' // Ref: http://hl7.org/fhir/STU3/http.html#patch const customHeaders = deprecateHeaders(options, headers).headers || {}; const requestHeaders = { ...customHeaders, 'Content-Type': 'application/json-patch+json' }; return this.httpClient.patch( `${resourceType}/${id}`, JSONPatch, { ...options, headers: requestHeaders }, ); } /** * Submit a set of actions to perform independently as a batch. * * Update, create or delete a set of resources in a single interaction. * There should be no interdependencies between entries in the bundle. * * @example * * const requestBundle = { * 'resourceType': 'Bundle', * 'type': 'batch', * 'entry': [ * { * 'fullUrl': 'http://example.org/fhir/Patient/123', * 'resource': { * 'resourceType': 'Patient', * 'id': '123', * 'active': true * }, * 'request': { * 'method': 'PUT', * 'url': 'Patient/123' * } * }, * { * 'request': { * 'method': 'DELETE', * 'url': 'Patient/2e27c71e-30c8-4ceb-8c1c-5641e066c0a4' * } * }, * { * 'request': { * 'method': 'GET', * 'url': 'Patient?name=peter' * } * } * ] * } * * // Using promises * fhirClient.batch({ * body: requestBundle * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.batch({ * body: requestBundle * }); * console.log(response); * * @param {Object} params - The request parameters. * @param {string} params.body - The request body with a type of 'batch'. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. */ batch({ body, headers, options = {} } = {}) { return this.httpClient.post('/', body, deprecateHeaders(options, headers)); } /** * Submit a set of actions to perform independently as a transaction. * * Update, create or delete a set of resources in a single interaction. * The entire set of changes should succeed or fail as a single entity. * Multiple actions on multiple resources different types may be submitted. * The outcome should not depend on the order of the resources loaded. * Order of processing actions: DELETE, POST, PUT, and GET. * The transaction fails if any resource overlap in DELETE, POST and PUT. * * @example * * const requestBundle = { * 'resourceType': 'Bundle', * 'type': 'transaction', * 'entry': [ * { * 'fullUrl': 'http://example.org/fhir/Patient/123', * 'resource': { * 'resourceType': 'Patient', * 'id': '123', * 'active': true * }, * 'request': { * 'method': 'PUT', * 'url': 'Patient/123' * } * }, * { * 'request': { * 'method': 'DELETE', * 'url': 'Patient/2e27c71e-30c8-4ceb-8c1c-5641e066c0a4' * } * }, * { * 'request': { * 'method': 'GET', * 'url': 'Patient?name=peter' * } * } * ] * } * * // Using promises * fhirClient.transaction({ * body: requestBundle * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.transaction({ * body: requestBundle * }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.body - The request body with a type of * 'transaction'. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. */ transaction({ body, headers, options = {} } = {}) { return this.httpClient.post('/', body, deprecateHeaders(options, headers)); } /** * Run a custom FHIR operation on system, resource type or instance level. * * - To run a system-level operation, omit the resourceType and id parameters. * - To run a type-level operatiion, include the resourceType and omit the id parameter. * - To run an instance-type operation, include both the resourceType and id. * * @example * * client.operation({ resourceType: 'ConceptMap', name: '$apply' }). * then(result => console.log(result). * catch(e => console.error(e)); * * * const input = { * system: 'http://hl7.org/fhir/composition-status', * code: 'preliminary', * source: 'http://hl7.org/fhir/ValueSet/composition-status', * target: 'http://hl7.org/fhir/ValueSet/v3-ActStatus' * }; * * client.operation({resourceType: 'ConceptMap', name: 'translate', method: 'GET', input}). * then(result => console.log(result)). * catch(e => console.error(e)); * * @param {String} params.name - The name of the operation (will get * prepended with $ if missing. * @param {String} [params.resourceType] - Optional The resource type (e.g. "Patient", * "Observation") * @param {String} [params.id] - Optional FHIR id for the resource * @param {String} [params.method] - Optional The HTTP method (post or get, defaults to post) * @param {Object} [params.input] - Optional input object for the operation * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} Result of opeartion (e.g. FHIR Parameter) */ operation({ name, resourceType, id, method = 'POST', input, options = {} } = {}) { const url = ['/']; if (resourceType) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } url.push(`${resourceType}/`); } if (id) { url.push(`${id}/`); } url.push(`${name.startsWith('$') ? name : `$${name}`}`); if (method.toUpperCase() === 'POST') { return this.httpClient.post(url.join(''), input, options); } if (method.toUpperCase() === 'GET') { if (input) { url.push(`?${queryString.stringify(input)}`); } return this.httpClient.get(url.join(''), options); } } /** * Return the next page of results. * * @param {Object} params - The request parameters. Passing the bundle as the * first parameter is DEPRECATED * @param {object} params.bundle - Bundle result of a FHIR search * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * @param {Object} [headers] - DEPRECATED Optional custom headers to add to * the request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. */ nextPage(params, headers) { const { bundle, options = {} } = deprecatePaginationArgs(params, headers); return this.pagination.nextPage(bundle, options); } /** * Return the previous page of results. * * @param {Object} params - The request parameters. Passing the bundle as the * first parameter is DEPRECATED * @param {object} params.bundle - Bundle result of a FHIR search * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * @param {Object} [headers] - DEPRECATED Optional custom headers to add to * the request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. */ prevPage(params, headers) { const { bundle, options = {} } = deprecatePaginationArgs(params, headers); return this.pagination.prevPage(bundle, options); } /** * Search for a FHIR resource, with or without compartments, or the entire * system * * @example * * // Using promises * fhirClient.search({ * resourceType: 'Observation', * compartment: { resourceType: 'Patient', id: 123 }, * searchParams: { code: 'abc', _include: ['Observation:encounter', 'Observation:performer'] }, * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.search({ * resourceType: 'Observation', * compartment: { resourceType: 'Patient', id: 123 }, * searchParams: { code: 'abc', _include: ['Observation:encounter', 'Observation:performer'] }, * }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} [params.resourceType] - The resource type * (e.g. "Patient", "Observation"), optional. * @param {Object} [params.compartment] - The search compartment, optional. * @param {Object} [params.searchParams] - The search parameters, optional. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * @param {Boolean} [params.options.postSearch] - if true, all `searchParams` * will be placed in the request body rather than the url, and the search * will use POST rather than GET * * @return {Promise<Object>} FHIR resources in a Bundle * * @throws {Error} if neither searchParams nor resourceType are supplied */ search({ resourceType, compartment, searchParams, headers, options = {} } = {}) { if (resourceType && !validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } if (compartment && resourceType) { return this.compartmentSearch({ resourceType, compartment, searchParams, options: deprecateHeaders(options, headers), }); } if (resourceType) { return this.resourceSearch({ resourceType, searchParams, options: deprecateHeaders(options, headers), }); } if (searchParams instanceof Object && Object.keys(searchParams).length > 0) { return this.systemSearch({ searchParams, options: deprecateHeaders(options, headers), }); } throw new Error('search requires either searchParams or a resourceType'); } /** * Search for a FHIR resource. * * @example * * // Using promises * fhirClient.resourceSearch({ * resourceType: 'Patient', * searchParams: { name: 'Smith' }, * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.resourceSearch({ * resourceType: 'Patient', * searchParams: { name: 'Smith' }, * }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.resourceType - The resource type (e.g. "Patient", * "Observation"). * @param {Object} params.searchParams - The search parameters. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * @param {Boolean} [params.options.postSearch] - if true, all `searchParams` * will be placed in the request body rather than the url, and the search * will use POST rather than GET * * @return {Promise<Object>} FHIR resources in a Bundle */ resourceSearch({ resourceType, searchParams, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } let searchPath = resourceType; if (options.postSearch) { searchPath += '/_search'; } return this.baseSearch({ searchPath, searchParams, headers, options }); } /** * Search across all FHIR resource types in the system. * Only the parameters defined for all resources can be used. * * @example * * // Using promises * fhirClient.systemSearch({ * searchParams: { name: 'smith' } * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.systemSearch({ searchParams: { name: 'smith' } }); * console.log(response); * * @param {Object} params - The request parameters. * @param {Object} params.searchParams - The search parameters. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * @param {Boolean} [params.options.postSearch] - if true, all `searchParams` * will be placed in the request body rather than the url, and the search * will use POST rather than GET * * @return {Promise<Object>} FHIR resources in a Bundle */ systemSearch({ searchParams, headers, options = {} } = {}) { const searchPath = '/_search'; return this.baseSearch({ searchPath, searchParams, headers, options }); } /** * Search for FHIR resources within a compartment. * The resourceType and id must be specified. * * @example * * // Using promises * fhirClient.compartmentSearch({ * resourceType: 'Observation', * compartment: { resourceType: 'Patient', id: 123 }, * searchParams: { code: 'abc' } * }).then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.compartmentSearch({ * resourceType: 'Observation', * compartment: { resourceType: 'Patient', id: 123 }, * searchParams: { code: 'abc' } * }); * console.log(response); * * @param {Object} params - The request parameters. * @param {String} params.resourceType - The resource type (e.g. "Patient", * "Observation"). * @param {Object} params.compartment - The search compartment. * @param {Object} [params.searchParams] - The search parameters, optional. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * @param {Boolean} [params.options.postSearch] - if true, all `searchParams` * will be placed in the request body rather than the url, and the search * will use POST rather than GET * * @return {Promise<Object>} FHIR resources in a Bundle */ compartmentSearch({ resourceType, compartment, searchParams, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } const { resourceType: compartmentType, id: compartmentId } = compartment; if (!validResourceType(compartmentType)) { throw new Error('Invalid compartmentType', compartmentType); } let searchPath = `/${compartmentType}/${compartmentId}/${resourceType}`; if (options.postSearch) { searchPath += '/_search'; } return this.baseSearch({ searchPath, searchParams, headers, options }); } /** * Perform a search * * @private * * @param {Object} params - The request parameters. * @param {String} params.searchPath - The url path * @param {Object} [params.searchParams] - The search parameters, optional * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * @param {Boolean} [params.options.postSearch] - if true, all `searchParams` * will be placed in the request body rather than the url, and the search * will use POST rather than GET * * @return {Promise<Object>} FHIR resources in a FHIR Bundle */ baseSearch({ searchPath, searchParams, headers, options }) { const searchQuery = createQueryString(searchParams); const searchOptions = deprecateHeaders(options, headers); const searchFunction = options.postSearch ? 'postSearch' : 'getSearch'; return this[searchFunction](searchPath, searchQuery, searchOptions); } /** * Perform a search using POST * * @private * * @param {String} path - The url path * @param {String} [query] - The query string, optional * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [options] - Optional options object * @param {Object} [options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle */ postSearch(path, query, options) { const defaultHeader = { 'Content-Type': 'application/x-www-form-urlencoded' }; const postHeaders = { ...defaultHeader, ...options.headers }; const postOptions = { ...options, headers: postHeaders }; return this.httpClient.post(path, query, postOptions); } /** * Perform a search using GET * * @private * * @param {String} path - The url path * @param {String} [query] - The query string, optional * @param {Object} [options] - Optional options object * @param {Object} [options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle */ getSearch(path, query, options) { let searchPath = path; if (query) { searchPath += `?${query}`; } return this.httpClient.get(searchPath, options); } /** * Retrieve the change history for a FHIR resource id, a resource type or the * entire system * * @example * * // Using promises * fhirClient.history({ resourceType: 'Patient', id: '12345' }); * .then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.history({ resourceType: 'Patient', id: '12345' }); * console.log(response); * * @param {Object} params - The request parameters. * @param {string} [params.resourceType] - The resource type * (e.g. "Patient", "Observation"), optional. * @param {string} [params.id] - The FHIR id for the resource, optional. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. */ history({ resourceType, id, headers, options = {} } = {}) { if (resourceType && !validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } if (id && resourceType) { return this.resourceHistory({ resourceType, id, options: deprecateHeaders(options, headers), }); } if (resourceType) { return this.typeHistory({ resourceType, options: deprecateHeaders(options, headers), }); } return this.systemHistory({ options: deprecateHeaders(options, headers) }); } /** * Retrieve the change history for a particular resource FHIR id. * * @example * * // Using promises * fhirClient.resourceHistory({ resourceType: 'Patient', id: '12345' }); * .then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.resourceHistory({ resourceType: 'Patient', id: '12345' }); * console.log(response); * * @param {Object} params - The request parameters. * @param {string} params.resourceType - The resource type (e.g. "Patient", * "Observation"). * @param {string} params.id - The FHIR id for the resource. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. */ resourceHistory({ resourceType, id, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } return this.httpClient.get( `${resourceType}/${id}/_history`, deprecateHeaders(options, headers), ); } /** * Retrieve the change history for a particular resource type. * * @example * * // Using promises * fhirClient.typeHistory({ resourceType: 'Patient' }); * .then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.typeHistory({ resourceType: 'Patient' }); * console.log(response); * * @param {Object} params - The request parameters. * @param {string} params.resourceType - The resource type (e.g. "Patient", * "Observation"). * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. */ typeHistory({ resourceType, headers, options = {} } = {}) { if (!validResourceType(resourceType)) { throw new Error('Invalid resourceType', resourceType); } return this.httpClient.get( `${resourceType}/_history`, deprecateHeaders(options, headers), ); } /** * Retrieve the change history for all resources. * * @example * * // Using promises * fhirClient.systemHistory(); * .then((data) => { console.log(data); }); * * // Using async * let response = await fhirClient.systemHistory(); * console.log(response); * * @param {Object} [params] - The request parameters. * @param {Object} [params.headers] - DEPRECATED Optional custom headers to * add to the request * @param {Object} [params.options] - Optional options object * @param {Object} [params.options.headers] - Optional headers to add to the * request * * @return {Promise<Object>} FHIR resources in a FHIR Bundle structure. */ systemHistory({ headers, options = {} } = {}) { return this.httpClient.get('_history', deprecateHeaders(options, headers)); } } module.exports = Client; module.exports.CapabilityTool = CapabilityTool;