@medplum/fhir-router 2.0.15 → 2.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/index.cjs +129 -10
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.min.cjs +1 -1
- package/dist/cjs/index.min.cjs.map +1 -1
- package/dist/esm/index.min.mjs +1 -1
- package/dist/esm/index.min.mjs.map +1 -1
- package/dist/esm/index.mjs +130 -11
- package/dist/esm/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/esm/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { OperationOutcomeError, badRequest, normalizeOperationOutcome, parseSearchUrl, getReferenceString, getStatus, isOk, allOk, LRUCache, forbidden, getResourceTypes, getResourceTypeSchema, isResourceTypeSchema, getElementDefinition, buildTypeName, capitalize, getSearchParameters, Operator, parseSearchRequest, notFound, created, deepClone, matchesSearchRequest,
|
|
1
|
+
import { OperationOutcomeError, badRequest, normalizeOperationOutcome, parseSearchUrl, getReferenceString, resolveId, getStatus, isOk, allOk, LRUCache, forbidden, getResourceTypes, getResourceTypeSchema, isResourceTypeSchema, getElementDefinition, buildTypeName, capitalize, isLowerCase, globalSchema, getSearchParameters, toJsBoolean, evalFhirPathTyped, toTypedValue, Operator, parseSearchRequest, notFound, created, deepClone, matchesSearchRequest, evalFhirPath } from '@medplum/core';
|
|
2
2
|
import DataLoader from 'dataloader';
|
|
3
3
|
import { applyPatch } from 'rfc6902';
|
|
4
4
|
|
|
@@ -53,6 +53,11 @@ class BatchProcessor {
|
|
|
53
53
|
const resultEntries = [];
|
|
54
54
|
for (const entry of entries) {
|
|
55
55
|
const rewritten = this.rewriteIdsInObject(entry);
|
|
56
|
+
// If the resource 'id' element is specified, we want to replace teh `urn:uuid:*` string and
|
|
57
|
+
// remove the `resourceType` prefix
|
|
58
|
+
if (entry?.resource?.id) {
|
|
59
|
+
rewritten.resource.id = this.rewriteIdsInString(entry.resource.id, true);
|
|
60
|
+
}
|
|
56
61
|
try {
|
|
57
62
|
resultEntries.push(await this.processBatchEntry(rewritten));
|
|
58
63
|
}
|
|
@@ -156,13 +161,17 @@ class BatchProcessor {
|
|
|
156
161
|
rewriteIdsInObject(input) {
|
|
157
162
|
return Object.fromEntries(Object.entries(input).map(([k, v]) => [k, this.rewriteIds(v)]));
|
|
158
163
|
}
|
|
159
|
-
rewriteIdsInString(input) {
|
|
164
|
+
rewriteIdsInString(input, removeResourceType = false) {
|
|
160
165
|
const matches = input.match(/urn:uuid:\w{8}-\w{4}-\w{4}-\w{4}-\w{12}/);
|
|
161
166
|
if (matches) {
|
|
162
167
|
const fullUrl = matches[0];
|
|
163
168
|
const resource = this.ids[fullUrl];
|
|
164
169
|
if (resource) {
|
|
165
|
-
|
|
170
|
+
let referenceString = getReferenceString(resource);
|
|
171
|
+
if (removeResourceType) {
|
|
172
|
+
referenceString = resolveId({ reference: referenceString });
|
|
173
|
+
}
|
|
174
|
+
return referenceString ? input.replaceAll(fullUrl, referenceString) : input;
|
|
166
175
|
}
|
|
167
176
|
}
|
|
168
177
|
return input;
|
|
@@ -13522,17 +13531,95 @@ function buildPropertyFields(resourceType, fields) {
|
|
|
13522
13531
|
for (const key of Object.keys(properties)) {
|
|
13523
13532
|
const elementDefinition = getElementDefinition(resourceType, key);
|
|
13524
13533
|
for (const type of elementDefinition.type) {
|
|
13525
|
-
|
|
13526
|
-
|
|
13527
|
-
|
|
13534
|
+
buildPropertyField(fields, key, elementDefinition, type);
|
|
13535
|
+
}
|
|
13536
|
+
}
|
|
13537
|
+
}
|
|
13538
|
+
function buildPropertyField(fields, key, elementDefinition, elementDefinitionType) {
|
|
13539
|
+
let typeName = elementDefinitionType.code;
|
|
13540
|
+
if (typeName === 'Element' || typeName === 'BackboneElement') {
|
|
13541
|
+
typeName = buildTypeName(elementDefinition.path?.split('.'));
|
|
13542
|
+
}
|
|
13543
|
+
const fieldConfig = {
|
|
13544
|
+
description: elementDefinition.short,
|
|
13545
|
+
type: getPropertyType(elementDefinition, typeName),
|
|
13546
|
+
resolve: resolveField,
|
|
13547
|
+
};
|
|
13548
|
+
if (elementDefinition.max === '*') {
|
|
13549
|
+
fieldConfig.args = buildListPropertyFieldArgs(typeName);
|
|
13550
|
+
}
|
|
13551
|
+
const propertyName = key.replace('[x]', capitalize(elementDefinitionType.code));
|
|
13552
|
+
fields[propertyName] = fieldConfig;
|
|
13553
|
+
}
|
|
13554
|
+
/**
|
|
13555
|
+
* Builds field arguments for a list property.
|
|
13556
|
+
*
|
|
13557
|
+
* The FHIR GraphQL specification defines the following arguments for list properties:
|
|
13558
|
+
* 1. _count: Specify how many elements to return from a repeating list.
|
|
13559
|
+
* 2. _offset: Specify the offset to start at for a repeating element.
|
|
13560
|
+
* 3. fhirpath: A FHIRPath statement selecting which of the subnodes is to be included.
|
|
13561
|
+
* 4. All properties of the list element type.
|
|
13562
|
+
*
|
|
13563
|
+
* See: https://hl7.org/fhir/R4/graphql.html#list
|
|
13564
|
+
*
|
|
13565
|
+
* @param fieldTypeName The type name of the field.
|
|
13566
|
+
* @returns The arguments for the field.
|
|
13567
|
+
*/
|
|
13568
|
+
function buildListPropertyFieldArgs(fieldTypeName) {
|
|
13569
|
+
const fieldArgs = {
|
|
13570
|
+
_count: {
|
|
13571
|
+
type: GraphQLInt,
|
|
13572
|
+
description: 'Specify how many elements to return from a repeating list.',
|
|
13573
|
+
},
|
|
13574
|
+
_offset: {
|
|
13575
|
+
type: GraphQLInt,
|
|
13576
|
+
description: 'Specify the offset to start at for a repeating element.',
|
|
13577
|
+
},
|
|
13578
|
+
};
|
|
13579
|
+
if (!isLowerCase(fieldTypeName.charAt(0))) {
|
|
13580
|
+
// If this is a backbone element, add "fhirpath" and all properties as arguments
|
|
13581
|
+
fieldArgs.fhirpath = {
|
|
13582
|
+
type: GraphQLString,
|
|
13583
|
+
description: 'A FHIRPath statement selecting which of the subnodes is to be included',
|
|
13584
|
+
};
|
|
13585
|
+
// Add all "string" and "code" properties as arguments
|
|
13586
|
+
const fieldTypeSchema = globalSchema.types[fieldTypeName];
|
|
13587
|
+
if (fieldTypeSchema.properties) {
|
|
13588
|
+
for (const fieldKey of Object.keys(fieldTypeSchema.properties)) {
|
|
13589
|
+
const fieldElementDefinition = getElementDefinition(fieldTypeName, fieldKey);
|
|
13590
|
+
for (const type of fieldElementDefinition.type) {
|
|
13591
|
+
buildListPropertyFieldArg(fieldArgs, fieldKey, fieldElementDefinition, type);
|
|
13592
|
+
}
|
|
13528
13593
|
}
|
|
13529
|
-
|
|
13594
|
+
}
|
|
13595
|
+
}
|
|
13596
|
+
return fieldArgs;
|
|
13597
|
+
}
|
|
13598
|
+
/**
|
|
13599
|
+
* Builds a field argument for a list property.
|
|
13600
|
+
* @param fieldArgs The output argument map.
|
|
13601
|
+
* @param fieldKey The key of the field.
|
|
13602
|
+
* @param elementDefinition The FHIR element definition of the field.
|
|
13603
|
+
* @param elementDefinitionType The FHIR element definition type of the field.
|
|
13604
|
+
*/
|
|
13605
|
+
function buildListPropertyFieldArg(fieldArgs, fieldKey, elementDefinition, elementDefinitionType) {
|
|
13606
|
+
const baseType = elementDefinitionType.code;
|
|
13607
|
+
const fieldName = fieldKey.replace('[x]', capitalize(baseType));
|
|
13608
|
+
switch (baseType) {
|
|
13609
|
+
case 'canonical':
|
|
13610
|
+
case 'code':
|
|
13611
|
+
case 'id':
|
|
13612
|
+
case 'oid':
|
|
13613
|
+
case 'string':
|
|
13614
|
+
case 'uri':
|
|
13615
|
+
case 'url':
|
|
13616
|
+
case 'uuid':
|
|
13617
|
+
case 'http://hl7.org/fhirpath/System.String':
|
|
13618
|
+
fieldArgs[fieldName] = {
|
|
13619
|
+
type: GraphQLString,
|
|
13530
13620
|
description: elementDefinition.short,
|
|
13531
|
-
type: getPropertyType(elementDefinition, typeName),
|
|
13532
13621
|
};
|
|
13533
|
-
|
|
13534
|
-
fields[propertyName] = fieldConfig;
|
|
13535
|
-
}
|
|
13622
|
+
break;
|
|
13536
13623
|
}
|
|
13537
13624
|
}
|
|
13538
13625
|
/**
|
|
@@ -13706,6 +13793,38 @@ function resolveTypeByReference(resource) {
|
|
|
13706
13793
|
}
|
|
13707
13794
|
return getGraphQLType(resourceType).name;
|
|
13708
13795
|
}
|
|
13796
|
+
/**
|
|
13797
|
+
* GraphQL resolver for fields.
|
|
13798
|
+
* In the common case, this is just a matter of returning the field value from the source object.
|
|
13799
|
+
* If the field is a list and the user specifies list arguments, then we can apply those arguments here.
|
|
13800
|
+
* @param source The source. This is the object that contains the field.
|
|
13801
|
+
* @param args The GraphQL search arguments.
|
|
13802
|
+
* @param _ctx The GraphQL context.
|
|
13803
|
+
* @param info The GraphQL resolve info. This includes the field name.
|
|
13804
|
+
* @returns Promise to read the resoure for the query.
|
|
13805
|
+
* @implements {GraphQLFieldResolver}
|
|
13806
|
+
*/
|
|
13807
|
+
async function resolveField(source, args, _ctx, info) {
|
|
13808
|
+
const fieldValue = source?.[info.fieldName];
|
|
13809
|
+
if (!args || !fieldValue) {
|
|
13810
|
+
return fieldValue;
|
|
13811
|
+
}
|
|
13812
|
+
const { _offset, _count, fhirpath, ...rest } = args;
|
|
13813
|
+
let array = fieldValue;
|
|
13814
|
+
for (const [key, value] of Object.entries(rest)) {
|
|
13815
|
+
array = array.filter((item) => item[key] === value);
|
|
13816
|
+
}
|
|
13817
|
+
if (fhirpath) {
|
|
13818
|
+
array = array.filter((item) => toJsBoolean(evalFhirPathTyped(fhirpath, [toTypedValue(item)])));
|
|
13819
|
+
}
|
|
13820
|
+
if (_offset) {
|
|
13821
|
+
array = array.slice(_offset);
|
|
13822
|
+
}
|
|
13823
|
+
if (_count) {
|
|
13824
|
+
array = array.slice(0, _count);
|
|
13825
|
+
}
|
|
13826
|
+
return array;
|
|
13827
|
+
}
|
|
13709
13828
|
function parseSearchArgs(resourceType, source, args) {
|
|
13710
13829
|
let referenceFilter = undefined;
|
|
13711
13830
|
if (source) {
|