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