@medplum/core 2.0.18 → 2.0.20

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.
@@ -1098,6 +1098,18 @@
1098
1098
  }
1099
1099
  return Math.round(a * Math.pow(10, precision));
1100
1100
  }
1101
+ /**
1102
+ * Finds the first resource in the input array that matches the specified code and system.
1103
+ * @param resources - The array of resources to search.
1104
+ * @param code - The code to search for.
1105
+ * @param system - The system to search for.
1106
+ * @returns The first resource in the input array that matches the specified code and system, or undefined if no such resource is found.
1107
+ */
1108
+ function findResourceByCode(resources, code, system) {
1109
+ return resources.find((r) => typeof code === 'string'
1110
+ ? getCodeBySystem(r.code || {}, system) === code
1111
+ : getCodeBySystem(r.code || {}, system) === getCodeBySystem(code, system));
1112
+ }
1101
1113
 
1102
1114
  /**
1103
1115
  * Returns a cryptographically secure random string.
@@ -6404,7 +6416,8 @@
6404
6416
  const globalSchema = baseSchema;
6405
6417
 
6406
6418
  // PKCE auth based on:
6407
- const MEDPLUM_VERSION = "2.0.18-e7b9bd9c" ;
6419
+ // https://aws.amazon.com/blogs/security/how-to-add-authentication-single-page-web-application-with-amazon-cognito-oauth2-implementation/
6420
+ const MEDPLUM_VERSION = "2.0.20-effeb76c" ;
6408
6421
  const DEFAULT_BASE_URL = 'https://api.medplum.com/';
6409
6422
  const DEFAULT_RESOURCE_CACHE_SIZE = 1000;
6410
6423
  const DEFAULT_CACHE_TIME = 60000; // 60 seconds
@@ -6575,6 +6588,13 @@
6575
6588
  url = url.toString();
6576
6589
  this.requestCache?.delete(url);
6577
6590
  }
6591
+ /**
6592
+ * Invalidates all cached values and flushes the cache.
6593
+ * @category Caching
6594
+ */
6595
+ invalidateAll() {
6596
+ this.requestCache?.clear();
6597
+ }
6578
6598
  /**
6579
6599
  * Invalidates all cached search results or cached requests for the given resourceType.
6580
6600
  * @category Caching
@@ -7906,6 +7926,26 @@
7906
7926
  const response = await this.fetch(url.toString(), options);
7907
7927
  return response.blob();
7908
7928
  }
7929
+ /**
7930
+ * Upload media to the server and create a Media instance for the uploaded content.
7931
+ * @param contents The contents of the media file, as a string, Uint8Array, File, or Blob.
7932
+ * @param contentType The media type of the content
7933
+ * @param filename The name of the file to be uploaded, or undefined if not applicable
7934
+ * @param additionalFields Additional fields for Media
7935
+ * @returns Promise that resolves to the created Media
7936
+ */
7937
+ async uploadMedia(contents, contentType, filename, additionalFields) {
7938
+ const binary = await this.createBinary(contents, filename, contentType);
7939
+ return this.createResource({
7940
+ ...additionalFields,
7941
+ resourceType: 'Media',
7942
+ content: {
7943
+ contentType: contentType,
7944
+ url: 'Binary/' + binary.id,
7945
+ title: filename,
7946
+ },
7947
+ });
7948
+ }
7909
7949
  //
7910
7950
  // Private helpers
7911
7951
  //
@@ -7981,7 +8021,10 @@
7981
8021
  return undefined;
7982
8022
  }
7983
8023
  if (response.status === 404) {
7984
- throw new OperationOutcomeError(normalizeOperationOutcome(notFound));
8024
+ const contentType = response.headers.get('content-type');
8025
+ if (!contentType?.includes('application/fhir+json')) {
8026
+ throw new OperationOutcomeError(notFound);
8027
+ }
7985
8028
  }
7986
8029
  let obj = undefined;
7987
8030
  try {
@@ -11394,157 +11437,572 @@
11394
11437
  return new StructureMapParser(parser).parse();
11395
11438
  }
11396
11439
 
11397
- const MAPPING_LANGUAGE_OPERATORS = [...FHIRPATH_OPERATORS, 'eq', 'ne', 'co'];
11398
- function tokenize(str) {
11399
- return new Tokenizer(str, FHIRPATH_KEYWORDS, MAPPING_LANGUAGE_OPERATORS, {
11400
- dateTimeLiterals: true,
11401
- symbolRegex: /[^\s\])]/,
11402
- }).tokenize();
11403
- }
11404
-
11405
- // See: https://hl7.org/fhir/search_filter.html
11440
+ const DEFAULT_SEARCH_COUNT = 20;
11406
11441
  /**
11407
- * The FhirFilterComparison class represents a comparison expression.
11442
+ * Search operators.
11443
+ * These operators represent "modifiers" and "prefixes" in FHIR search.
11444
+ * See: https://www.hl7.org/fhir/search.html
11408
11445
  */
11409
- class FhirFilterComparison {
11410
- constructor(path, operator, value) {
11411
- this.path = path;
11412
- this.operator = operator;
11413
- this.value = value;
11414
- }
11415
- }
11446
+ exports.Operator = void 0;
11447
+ (function (Operator) {
11448
+ Operator["EQUALS"] = "eq";
11449
+ Operator["NOT_EQUALS"] = "ne";
11450
+ // Numbers
11451
+ Operator["GREATER_THAN"] = "gt";
11452
+ Operator["LESS_THAN"] = "lt";
11453
+ Operator["GREATER_THAN_OR_EQUALS"] = "ge";
11454
+ Operator["LESS_THAN_OR_EQUALS"] = "le";
11455
+ // Dates
11456
+ Operator["STARTS_AFTER"] = "sa";
11457
+ Operator["ENDS_BEFORE"] = "eb";
11458
+ Operator["APPROXIMATELY"] = "ap";
11459
+ // String
11460
+ Operator["CONTAINS"] = "contains";
11461
+ Operator["EXACT"] = "exact";
11462
+ // Token
11463
+ Operator["TEXT"] = "text";
11464
+ Operator["NOT"] = "not";
11465
+ Operator["ABOVE"] = "above";
11466
+ Operator["BELOW"] = "below";
11467
+ Operator["IN"] = "in";
11468
+ Operator["NOT_IN"] = "not-in";
11469
+ Operator["OF_TYPE"] = "of-type";
11470
+ // All
11471
+ Operator["MISSING"] = "missing";
11472
+ // Reference
11473
+ Operator["IDENTIFIER"] = "identifier";
11474
+ // _include and _revinclude
11475
+ Operator["ITERATE"] = "iterate";
11476
+ })(exports.Operator || (exports.Operator = {}));
11416
11477
  /**
11417
- * The FhirFilterNegation class represents a negation expression.
11418
- * It contains a single child expression.
11478
+ * Parameter names may specify a modifier as a suffix.
11479
+ * The modifiers are separated from the parameter name by a colon.
11480
+ * See: https://www.hl7.org/fhir/search.html#modifiers
11419
11481
  */
11420
- class FhirFilterNegation {
11421
- constructor(child) {
11422
- this.child = child;
11423
- }
11424
- }
11482
+ const MODIFIER_OPERATORS = {
11483
+ contains: exports.Operator.CONTAINS,
11484
+ exact: exports.Operator.EXACT,
11485
+ above: exports.Operator.ABOVE,
11486
+ below: exports.Operator.BELOW,
11487
+ text: exports.Operator.TEXT,
11488
+ not: exports.Operator.NOT,
11489
+ in: exports.Operator.IN,
11490
+ 'not-in': exports.Operator.NOT_IN,
11491
+ 'of-type': exports.Operator.OF_TYPE,
11492
+ missing: exports.Operator.MISSING,
11493
+ identifier: exports.Operator.IDENTIFIER,
11494
+ iterate: exports.Operator.ITERATE,
11495
+ };
11425
11496
  /**
11426
- * The FhirFilterConnective class represents a connective expression.
11427
- * It contains a list of child expressions.
11497
+ * For the ordered parameter types of number, date, and quantity,
11498
+ * a prefix to the parameter value may be used to control the nature
11499
+ * of the matching.
11500
+ * See: https://www.hl7.org/fhir/search.html#prefix
11428
11501
  */
11429
- class FhirFilterConnective {
11430
- constructor(keyword, left, right) {
11431
- this.keyword = keyword;
11432
- this.left = left;
11433
- this.right = right;
11434
- }
11435
- }
11436
-
11437
- class FilterParameterParser {
11438
- constructor(parser) {
11439
- this.parser = parser;
11440
- }
11441
- parse() {
11442
- let result;
11443
- if (this.parser.peek()?.value === '(') {
11444
- this.parser.consume('(');
11445
- result = this.parse();
11446
- this.parser.consume(')');
11447
- }
11448
- else if (this.parser.peek()?.value === 'not') {
11449
- this.parser.consume('Symbol', 'not');
11450
- this.parser.consume('(');
11451
- result = new FhirFilterNegation(this.parse());
11452
- this.parser.consume(')');
11502
+ const PREFIX_OPERATORS = {
11503
+ eq: exports.Operator.EQUALS,
11504
+ ne: exports.Operator.NOT_EQUALS,
11505
+ lt: exports.Operator.LESS_THAN,
11506
+ le: exports.Operator.LESS_THAN_OR_EQUALS,
11507
+ gt: exports.Operator.GREATER_THAN,
11508
+ ge: exports.Operator.GREATER_THAN_OR_EQUALS,
11509
+ sa: exports.Operator.STARTS_AFTER,
11510
+ eb: exports.Operator.ENDS_BEFORE,
11511
+ ap: exports.Operator.APPROXIMATELY,
11512
+ };
11513
+ /**
11514
+ * Parses a search URL into a search request.
11515
+ * @param resourceType The FHIR resource type.
11516
+ * @param query The collection of query string parameters.
11517
+ * @returns A parsed SearchRequest.
11518
+ */
11519
+ function parseSearchRequest(resourceType, query) {
11520
+ const queryArray = [];
11521
+ for (const [key, value] of Object.entries(query)) {
11522
+ if (Array.isArray(value)) {
11523
+ for (let i = 0; i < value.length; i++) {
11524
+ queryArray.push([key, value[i]]);
11525
+ }
11453
11526
  }
11454
11527
  else {
11455
- result = new FhirFilterComparison(this.parser.consume('Symbol').value, this.parser.consume('Symbol').value, this.parser.consume().value);
11456
- }
11457
- const next = this.parser.peek()?.value;
11458
- if (next === 'and' || next === 'or') {
11459
- this.parser.consume('Symbol', next);
11460
- return new FhirFilterConnective(next, result, this.parse());
11528
+ queryArray.push([key, value || '']);
11461
11529
  }
11462
- return result;
11463
11530
  }
11531
+ return parseSearchImpl(resourceType, queryArray);
11464
11532
  }
11465
- const fhirPathParserBuilder = initFhirPathParserBuilder();
11466
11533
  /**
11467
- * Parses a FHIR _filter parameter expression into an AST.
11468
- * @param input The FHIR _filter parameter expression.
11469
- * @returns The AST representing the filters.
11534
+ * Parses a search URL into a search request.
11535
+ * @param url The search URL.
11536
+ * @returns A parsed SearchRequest.
11470
11537
  */
11471
- function parseFilterParameter(input) {
11472
- const parser = fhirPathParserBuilder.construct(tokenize(input));
11473
- parser.removeComments();
11474
- return new FilterParameterParser(parser).parse();
11538
+ function parseSearchUrl(url) {
11539
+ const resourceType = url.pathname.split('/').filter(Boolean).pop();
11540
+ return parseSearchImpl(resourceType, url.searchParams.entries());
11475
11541
  }
11476
-
11477
11542
  /**
11478
- * The Hl7Context class represents the parsing context for an HL7 message.
11479
- *
11480
- * MSH-1:
11481
- * https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.1
11482
- *
11483
- * MSH-2:
11484
- * https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.2
11485
- *
11486
- * See this tutorial on MSH, and why it's a bad idea to use anything other than the default values:
11487
- * https://www.hl7soup.com/HL7TutorialMSH.html
11543
+ * Parses a URL string into a SearchRequest.
11544
+ * @param url The URL to parse.
11545
+ * @returns Parsed search definition.
11488
11546
  */
11489
- class Hl7Context {
11490
- constructor(segmentSeparator = '\r', fieldSeparator = '|', componentSeparator = '^', repetitionSeparator = '~', escapeCharacter = '\\', subcomponentSeparator = '&') {
11491
- this.segmentSeparator = segmentSeparator;
11492
- this.fieldSeparator = fieldSeparator;
11493
- this.componentSeparator = componentSeparator;
11494
- this.repetitionSeparator = repetitionSeparator;
11495
- this.escapeCharacter = escapeCharacter;
11496
- this.subcomponentSeparator = subcomponentSeparator;
11497
- }
11498
- /**
11499
- * Returns the MSH-2 field value based on the configured separators.
11500
- * @returns The HL7 MSH-2 field value.
11501
- */
11502
- getMsh2() {
11503
- return (this.fieldSeparator +
11504
- this.componentSeparator +
11505
- this.repetitionSeparator +
11506
- this.escapeCharacter +
11507
- this.subcomponentSeparator);
11508
- }
11547
+ function parseSearchDefinition(url) {
11548
+ return parseSearchUrl(new URL(url, 'https://example.com/'));
11509
11549
  }
11510
- /**
11511
- * The Hl7Message class represents one HL7 message.
11512
- * A message is a collection of segments.
11513
- */
11514
- class Hl7Message {
11515
- /**
11516
- * Creates a new HL7 message.
11517
- * @param segments The HL7 segments.
11518
- * @param context Optional HL7 parsing context.
11519
- */
11520
- constructor(segments, context = new Hl7Context()) {
11521
- this.context = context;
11522
- this.segments = segments;
11550
+ function parseSearchImpl(resourceType, query) {
11551
+ const searchRequest = {
11552
+ resourceType,
11553
+ };
11554
+ for (const [key, value] of query) {
11555
+ parseKeyValue(searchRequest, key, value);
11523
11556
  }
11524
- /**
11525
- * Returns an HL7 segment by index or by name.
11526
- * @param index The HL7 segment index or name.
11527
- * @returns The HL7 segment if found; otherwise, undefined.
11528
- */
11529
- get(index) {
11530
- if (typeof index === 'number') {
11531
- return this.segments[index];
11532
- }
11533
- return this.segments.find((s) => s.name === index);
11557
+ return searchRequest;
11558
+ }
11559
+ function parseKeyValue(searchRequest, key, value) {
11560
+ let code;
11561
+ let modifier;
11562
+ const colonIndex = key.indexOf(':');
11563
+ if (colonIndex >= 0) {
11564
+ code = key.substring(0, colonIndex);
11565
+ modifier = key.substring(colonIndex + 1);
11534
11566
  }
11535
- /**
11536
- * Returns all HL7 segments of a given name.
11537
- * @param name The HL7 segment name.
11538
- * @returns An array of HL7 segments with the specified name.
11539
- */
11540
- getAll(name) {
11541
- return this.segments.filter((s) => s.name === name);
11567
+ else {
11568
+ code = key;
11569
+ modifier = '';
11542
11570
  }
11543
- /**
11544
- * Returns the HL7 message as a string.
11545
- * @returns The HL7 message as a string.
11546
- */
11547
- toString() {
11571
+ switch (code) {
11572
+ case '_sort':
11573
+ parseSortRule(searchRequest, value);
11574
+ break;
11575
+ case '_count':
11576
+ searchRequest.count = parseInt(value);
11577
+ break;
11578
+ case '_offset':
11579
+ searchRequest.offset = parseInt(value);
11580
+ break;
11581
+ case '_total':
11582
+ searchRequest.total = value;
11583
+ break;
11584
+ case '_summary':
11585
+ searchRequest.total = 'estimate';
11586
+ searchRequest.count = 0;
11587
+ break;
11588
+ case '_include': {
11589
+ const target = parseIncludeTarget(value);
11590
+ if (modifier === 'iterate') {
11591
+ target.modifier = exports.Operator.ITERATE;
11592
+ }
11593
+ if (searchRequest.include) {
11594
+ searchRequest.include.push(target);
11595
+ }
11596
+ else {
11597
+ searchRequest.include = [target];
11598
+ }
11599
+ break;
11600
+ }
11601
+ case '_revinclude': {
11602
+ const target = parseIncludeTarget(value);
11603
+ if (modifier === 'iterate') {
11604
+ target.modifier = exports.Operator.ITERATE;
11605
+ }
11606
+ if (searchRequest.revInclude) {
11607
+ searchRequest.revInclude.push(target);
11608
+ }
11609
+ else {
11610
+ searchRequest.revInclude = [target];
11611
+ }
11612
+ break;
11613
+ }
11614
+ case '_fields':
11615
+ searchRequest.fields = value.split(',');
11616
+ break;
11617
+ default: {
11618
+ const param = globalSchema.types[searchRequest.resourceType]?.searchParams?.[code];
11619
+ if (param) {
11620
+ parseParameter(searchRequest, param, modifier, value);
11621
+ }
11622
+ else {
11623
+ parseUnknownParameter(searchRequest, code, modifier, value);
11624
+ }
11625
+ }
11626
+ }
11627
+ }
11628
+ function parseSortRule(searchRequest, value) {
11629
+ for (const field of value.split(',')) {
11630
+ let code;
11631
+ let descending = false;
11632
+ if (field.startsWith('-')) {
11633
+ code = field.substring(1);
11634
+ descending = true;
11635
+ }
11636
+ else {
11637
+ code = field;
11638
+ }
11639
+ if (!searchRequest.sortRules) {
11640
+ searchRequest.sortRules = [];
11641
+ }
11642
+ searchRequest.sortRules.push({ code, descending });
11643
+ }
11644
+ }
11645
+ function parseParameter(searchRequest, searchParam, modifier, value) {
11646
+ if (modifier === 'missing') {
11647
+ addFilter(searchRequest, {
11648
+ code: searchParam.code,
11649
+ operator: exports.Operator.MISSING,
11650
+ value,
11651
+ });
11652
+ return;
11653
+ }
11654
+ switch (searchParam.type) {
11655
+ case 'number':
11656
+ case 'date':
11657
+ parsePrefixType(searchRequest, searchParam, value);
11658
+ break;
11659
+ case 'reference':
11660
+ case 'string':
11661
+ case 'token':
11662
+ case 'uri':
11663
+ parseModifierType(searchRequest, searchParam, modifier, value);
11664
+ break;
11665
+ case 'quantity':
11666
+ parseQuantity(searchRequest, searchParam, value);
11667
+ break;
11668
+ }
11669
+ }
11670
+ function parsePrefixType(searchRequest, param, input) {
11671
+ const { operator, value } = parsePrefix(input);
11672
+ addFilter(searchRequest, {
11673
+ code: param.code,
11674
+ operator,
11675
+ value,
11676
+ });
11677
+ }
11678
+ function parseModifierType(searchRequest, param, modifier, value) {
11679
+ addFilter(searchRequest, {
11680
+ code: param.code,
11681
+ operator: parseModifier(modifier),
11682
+ value,
11683
+ });
11684
+ }
11685
+ function parseQuantity(searchRequest, param, input) {
11686
+ const [prefixNumber, unitSystem, unitCode] = input.split('|');
11687
+ const { operator, value } = parsePrefix(prefixNumber);
11688
+ addFilter(searchRequest, {
11689
+ code: param.code,
11690
+ operator,
11691
+ value,
11692
+ unitSystem,
11693
+ unitCode,
11694
+ });
11695
+ }
11696
+ function parseUnknownParameter(searchRequest, code, modifier, value) {
11697
+ let operator = exports.Operator.EQUALS;
11698
+ if (modifier) {
11699
+ operator = modifier;
11700
+ }
11701
+ else if (value.length >= 2) {
11702
+ const prefix = value.substring(0, 2);
11703
+ if (prefix in PREFIX_OPERATORS) {
11704
+ if (value.length === 2 || value.at(2)?.match(/\d/)) {
11705
+ operator = prefix;
11706
+ value = value.substring(prefix.length);
11707
+ }
11708
+ }
11709
+ }
11710
+ addFilter(searchRequest, {
11711
+ code,
11712
+ operator,
11713
+ value,
11714
+ });
11715
+ }
11716
+ function parsePrefix(input) {
11717
+ const prefix = input.substring(0, 2);
11718
+ const prefixOperator = PREFIX_OPERATORS[prefix];
11719
+ if (prefixOperator) {
11720
+ return { operator: prefixOperator, value: input.substring(2) };
11721
+ }
11722
+ return { operator: exports.Operator.EQUALS, value: input };
11723
+ }
11724
+ function parseModifier(modifier) {
11725
+ return MODIFIER_OPERATORS[modifier] || exports.Operator.EQUALS;
11726
+ }
11727
+ function parseIncludeTarget(input) {
11728
+ const parts = input.split(':');
11729
+ parts.forEach((p) => {
11730
+ if (p === '*') {
11731
+ throw new OperationOutcomeError(badRequest(`'*' is not supported as a value for search inclusion parameters`));
11732
+ }
11733
+ });
11734
+ if (parts.length === 1) {
11735
+ // Full wildcard, not currently supported
11736
+ throw new OperationOutcomeError(badRequest(`Invalid include value '${input}': must be of the form ResourceType:search-parameter`));
11737
+ }
11738
+ else if (parts.length === 2) {
11739
+ return {
11740
+ resourceType: parts[0],
11741
+ searchParam: parts[1],
11742
+ };
11743
+ }
11744
+ else if (parts.length === 3) {
11745
+ return {
11746
+ resourceType: parts[0],
11747
+ searchParam: parts[1],
11748
+ targetType: parts[2],
11749
+ };
11750
+ }
11751
+ else {
11752
+ throw new OperationOutcomeError(badRequest(`Invalid include value '${input}'`));
11753
+ }
11754
+ }
11755
+ function addFilter(searchRequest, filter) {
11756
+ if (searchRequest.filters) {
11757
+ searchRequest.filters.push(filter);
11758
+ }
11759
+ else {
11760
+ searchRequest.filters = [filter];
11761
+ }
11762
+ }
11763
+ /**
11764
+ * Formats a search definition object into a query string.
11765
+ * Note: The return value does not include the resource type.
11766
+ * @param {!SearchRequest} definition The search definition.
11767
+ * @returns Formatted URL.
11768
+ */
11769
+ function formatSearchQuery(definition) {
11770
+ const params = [];
11771
+ if (definition.fields) {
11772
+ params.push('_fields=' + definition.fields.join(','));
11773
+ }
11774
+ if (definition.filters) {
11775
+ definition.filters.forEach((filter) => params.push(formatFilter(filter)));
11776
+ }
11777
+ if (definition.sortRules && definition.sortRules.length > 0) {
11778
+ params.push(formatSortRules(definition.sortRules));
11779
+ }
11780
+ if (definition.offset !== undefined) {
11781
+ params.push('_offset=' + definition.offset);
11782
+ }
11783
+ if (definition.count !== undefined) {
11784
+ params.push('_count=' + definition.count);
11785
+ }
11786
+ if (definition.total !== undefined) {
11787
+ params.push('_total=' + definition.total);
11788
+ }
11789
+ if (params.length === 0) {
11790
+ return '';
11791
+ }
11792
+ params.sort();
11793
+ return '?' + params.join('&');
11794
+ }
11795
+ function formatFilter(filter) {
11796
+ const modifier = filter.operator in MODIFIER_OPERATORS ? ':' + filter.operator : '';
11797
+ const prefix = filter.operator !== exports.Operator.EQUALS && filter.operator in PREFIX_OPERATORS ? filter.operator : '';
11798
+ return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
11799
+ }
11800
+ function formatSortRules(sortRules) {
11801
+ return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
11802
+ }
11803
+
11804
+ const MAPPING_LANGUAGE_OPERATORS = [...FHIRPATH_OPERATORS, 'eq', 'ne', 'co'];
11805
+ function tokenize(str) {
11806
+ return new Tokenizer(str, FHIRPATH_KEYWORDS, MAPPING_LANGUAGE_OPERATORS, {
11807
+ dateTimeLiterals: true,
11808
+ symbolRegex: /[^\s\])]/,
11809
+ }).tokenize();
11810
+ }
11811
+
11812
+ // See: https://hl7.org/fhir/search_filter.html
11813
+ /**
11814
+ * The FhirFilterComparison class represents a comparison expression.
11815
+ */
11816
+ class FhirFilterComparison {
11817
+ constructor(path, operator, value) {
11818
+ this.path = path;
11819
+ this.operator = operator;
11820
+ this.value = value;
11821
+ }
11822
+ }
11823
+ /**
11824
+ * The FhirFilterNegation class represents a negation expression.
11825
+ * It contains a single child expression.
11826
+ */
11827
+ class FhirFilterNegation {
11828
+ constructor(child) {
11829
+ this.child = child;
11830
+ }
11831
+ }
11832
+ /**
11833
+ * The FhirFilterConnective class represents a connective expression.
11834
+ * It contains a list of child expressions.
11835
+ */
11836
+ class FhirFilterConnective {
11837
+ constructor(keyword, left, right) {
11838
+ this.keyword = keyword;
11839
+ this.left = left;
11840
+ this.right = right;
11841
+ }
11842
+ }
11843
+
11844
+ /**
11845
+ * The operatorMap maps FHIR _filter operators to Medplum search operators.
11846
+ * See _filter operators: https://www.hl7.org/fhir/search_filter.html#ops
11847
+ */
11848
+ const operatorMap = {
11849
+ // eq - an item in the set has an equal value
11850
+ eq: exports.Operator.EQUALS,
11851
+ // ne - An item in the set has an unequal value
11852
+ ne: exports.Operator.NOT_EQUALS,
11853
+ // co - An item in the set contains this value
11854
+ co: exports.Operator.CONTAINS,
11855
+ // sw - An item in the set starts with this value
11856
+ sw: undefined,
11857
+ // ew - An item in the set ends with this value
11858
+ ew: undefined,
11859
+ // gt / lt / ge / le - A value in the set is (greater than, less than, greater or equal, less or equal) the given value
11860
+ gt: exports.Operator.GREATER_THAN,
11861
+ lt: exports.Operator.LESS_THAN,
11862
+ ge: exports.Operator.GREATER_THAN_OR_EQUALS,
11863
+ le: exports.Operator.LESS_THAN_OR_EQUALS,
11864
+ // ap - A value in the set is approximately the same as this value.
11865
+ // Note that the recommended value for the approximation is 10% of the stated value (or for a date, 10% of the gap between now and the date), but systems may choose other values where appropriate
11866
+ ap: exports.Operator.APPROXIMATELY,
11867
+ // sa - The value starts after the specified value
11868
+ sa: exports.Operator.STARTS_AFTER,
11869
+ // eb - The value ends before the specified value
11870
+ eb: exports.Operator.ENDS_BEFORE,
11871
+ // pr - The set is empty or not (value is false or true)
11872
+ pr: exports.Operator.MISSING,
11873
+ // po - True if a (implied) date period in the set overlaps with the implied period in the value
11874
+ po: undefined,
11875
+ // ss - True if the value subsumes a concept in the set
11876
+ ss: undefined,
11877
+ // sb - True if the value is subsumed by a concept in the set
11878
+ sb: undefined,
11879
+ // in - True if one of the concepts is in the nominated value set by URI, either a relative, literal or logical vs
11880
+ in: exports.Operator.IN,
11881
+ // ni - True if none of the concepts are in the nominated value set by URI, either a relative, literal or logical vs
11882
+ ni: exports.Operator.NOT_IN,
11883
+ // re - True if one of the references in set points to the given URL
11884
+ re: undefined,
11885
+ // identifier - True if the identifier is in the identifier set (Medplum extension)
11886
+ identifier: exports.Operator.IDENTIFIER,
11887
+ };
11888
+ function getOperator(value) {
11889
+ const operator = operatorMap[value];
11890
+ if (!operator) {
11891
+ throw new OperationOutcomeError(badRequest('Invalid operator: ' + value));
11892
+ }
11893
+ return operator;
11894
+ }
11895
+ class FilterParameterParser {
11896
+ constructor(parser) {
11897
+ this.parser = parser;
11898
+ }
11899
+ parse() {
11900
+ let result;
11901
+ if (this.parser.peek()?.value === '(') {
11902
+ this.parser.consume('(');
11903
+ result = this.parse();
11904
+ this.parser.consume(')');
11905
+ }
11906
+ else if (this.parser.peek()?.value === 'not') {
11907
+ this.parser.consume('Symbol', 'not');
11908
+ this.parser.consume('(');
11909
+ result = new FhirFilterNegation(this.parse());
11910
+ this.parser.consume(')');
11911
+ }
11912
+ else {
11913
+ result = new FhirFilterComparison(this.parser.consume('Symbol').value, getOperator(this.parser.consume('Symbol').value), this.parser.consume().value);
11914
+ }
11915
+ const next = this.parser.peek()?.value;
11916
+ if (next === 'and' || next === 'or') {
11917
+ this.parser.consume('Symbol', next);
11918
+ return new FhirFilterConnective(next, result, this.parse());
11919
+ }
11920
+ return result;
11921
+ }
11922
+ }
11923
+ const fhirPathParserBuilder = initFhirPathParserBuilder();
11924
+ /**
11925
+ * Parses a FHIR _filter parameter expression into an AST.
11926
+ * @param input The FHIR _filter parameter expression.
11927
+ * @returns The AST representing the filters.
11928
+ */
11929
+ function parseFilterParameter(input) {
11930
+ const parser = fhirPathParserBuilder.construct(tokenize(input));
11931
+ parser.removeComments();
11932
+ return new FilterParameterParser(parser).parse();
11933
+ }
11934
+
11935
+ /**
11936
+ * The Hl7Context class represents the parsing context for an HL7 message.
11937
+ *
11938
+ * MSH-1:
11939
+ * https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.1
11940
+ *
11941
+ * MSH-2:
11942
+ * https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.2
11943
+ *
11944
+ * See this tutorial on MSH, and why it's a bad idea to use anything other than the default values:
11945
+ * https://www.hl7soup.com/HL7TutorialMSH.html
11946
+ */
11947
+ class Hl7Context {
11948
+ constructor(segmentSeparator = '\r', fieldSeparator = '|', componentSeparator = '^', repetitionSeparator = '~', escapeCharacter = '\\', subcomponentSeparator = '&') {
11949
+ this.segmentSeparator = segmentSeparator;
11950
+ this.fieldSeparator = fieldSeparator;
11951
+ this.componentSeparator = componentSeparator;
11952
+ this.repetitionSeparator = repetitionSeparator;
11953
+ this.escapeCharacter = escapeCharacter;
11954
+ this.subcomponentSeparator = subcomponentSeparator;
11955
+ }
11956
+ /**
11957
+ * Returns the MSH-2 field value based on the configured separators.
11958
+ * @returns The HL7 MSH-2 field value.
11959
+ */
11960
+ getMsh2() {
11961
+ return (this.fieldSeparator +
11962
+ this.componentSeparator +
11963
+ this.repetitionSeparator +
11964
+ this.escapeCharacter +
11965
+ this.subcomponentSeparator);
11966
+ }
11967
+ }
11968
+ /**
11969
+ * The Hl7Message class represents one HL7 message.
11970
+ * A message is a collection of segments.
11971
+ */
11972
+ class Hl7Message {
11973
+ /**
11974
+ * Creates a new HL7 message.
11975
+ * @param segments The HL7 segments.
11976
+ * @param context Optional HL7 parsing context.
11977
+ */
11978
+ constructor(segments, context = new Hl7Context()) {
11979
+ this.context = context;
11980
+ this.segments = segments;
11981
+ }
11982
+ /**
11983
+ * Returns an HL7 segment by index or by name.
11984
+ * @param index The HL7 segment index or name.
11985
+ * @returns The HL7 segment if found; otherwise, undefined.
11986
+ */
11987
+ get(index) {
11988
+ if (typeof index === 'number') {
11989
+ return this.segments[index];
11990
+ }
11991
+ return this.segments.find((s) => s.name === index);
11992
+ }
11993
+ /**
11994
+ * Returns all HL7 segments of a given name.
11995
+ * @param name The HL7 segment name.
11996
+ * @returns An array of HL7 segments with the specified name.
11997
+ */
11998
+ getAll(name) {
11999
+ return this.segments.filter((s) => s.name === name);
12000
+ }
12001
+ /**
12002
+ * Returns the HL7 message as a string.
12003
+ * @returns The HL7 message as a string.
12004
+ */
12005
+ toString() {
11548
12006
  return this.segments.map((s) => s.toString()).join(this.context.segmentSeparator);
11549
12007
  }
11550
12008
  /**
@@ -11689,6 +12147,28 @@
11689
12147
  return new Hl7Field(text.split(context.repetitionSeparator).map((r) => r.split(context.componentSeparator)), context);
11690
12148
  }
11691
12149
  }
12150
+ /**
12151
+ * Returns a formatted string representing the date in ISO-8601 format.
12152
+ * @param hl7Date Date string.
12153
+ * @param options Optional configuration Object
12154
+ * @returns
12155
+ */
12156
+ function parseHl7Date(hl7Date, options) {
12157
+ if (!hl7Date) {
12158
+ return undefined;
12159
+ }
12160
+ options = { ...{ seconds: true, tzOffset: 'Z' }, ...options };
12161
+ const year = Number.parseInt(hl7Date.substring(0, 4));
12162
+ const month = Number.parseInt(hl7Date.substring(4, 6));
12163
+ const date = Number.parseInt(hl7Date.substring(6, 8));
12164
+ const hours = Number.parseInt(hl7Date.substring(8, 10));
12165
+ const minutes = Number.parseInt(hl7Date.substring(10, 12));
12166
+ const seconds = options.seconds ? Number.parseInt(hl7Date.substring(12, 14)) : 0;
12167
+ return `${pad2(year)}-${pad2(month)}-${pad2(date)}T${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}.000${options.tzOffset}`;
12168
+ }
12169
+ function pad2(n) {
12170
+ return n.toString().padStart(2, '0');
12171
+ }
11692
12172
 
11693
12173
  /*
11694
12174
  * This file provides schema validation utilities for FHIR JSON objects.
@@ -12112,6 +12592,7 @@
12112
12592
  SearchParameterType["QUANTITY"] = "QUANTITY";
12113
12593
  SearchParameterType["TEXT"] = "TEXT";
12114
12594
  SearchParameterType["REFERENCE"] = "REFERENCE";
12595
+ SearchParameterType["CANONICAL"] = "CANONICAL";
12115
12596
  SearchParameterType["DATE"] = "DATE";
12116
12597
  SearchParameterType["DATETIME"] = "DATETIME";
12117
12598
  SearchParameterType["PERIOD"] = "PERIOD";
@@ -12154,474 +12635,114 @@
12154
12635
  // In the future, explore returning multiple column definitions
12155
12636
  return { columnName, type: exports.SearchParameterType.TEXT };
12156
12637
  }
12157
- const defaultType = getSearchParameterType(searchParam);
12158
- let baseType = resourceType;
12159
- let elementDefinition = undefined;
12160
- let propertyType = undefined;
12161
- let array = false;
12162
- for (let i = 1; i < expression.length; i++) {
12163
- const propertyName = expression[i];
12164
- elementDefinition = getElementDefinition(baseType, propertyName);
12165
- if (!elementDefinition) {
12166
- throw new Error(`Element definition not found for ${resourceType} ${searchParam.code}`);
12167
- }
12168
- if (elementDefinition.max === '*') {
12169
- array = true;
12170
- }
12171
- propertyType = elementDefinition.type?.[0].code;
12172
- if (!propertyType) {
12173
- // This happens when one of parent properties uses contentReference
12174
- // In the future, explore following the reference
12175
- return { columnName, type: defaultType, array };
12176
- }
12177
- if (i < expression.length - 1) {
12178
- if (isBackboneElement(propertyType)) {
12179
- baseType = buildTypeName(elementDefinition.path?.split('.'));
12180
- }
12181
- else {
12182
- baseType = propertyType;
12183
- }
12184
- }
12185
- }
12186
- const type = getSearchParameterType(searchParam, propertyType);
12187
- const result = { columnName, type, elementDefinition, array };
12188
- setSearchParamterDetails(resourceType, code, result);
12189
- return result;
12190
- }
12191
- function isBackboneElement(propertyType) {
12192
- return propertyType === 'Element' || propertyType === 'BackboneElement';
12193
- }
12194
- /**
12195
- * Converts a hyphen-delimited code to camelCase string.
12196
- * @param code The search parameter code.
12197
- * @returns The SQL column name.
12198
- */
12199
- function convertCodeToColumnName(code) {
12200
- return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
12201
- }
12202
- function getSearchParameterType(searchParam, propertyType) {
12203
- let type = exports.SearchParameterType.TEXT;
12204
- switch (searchParam.type) {
12205
- case 'date':
12206
- if (propertyType === exports.PropertyType.dateTime || propertyType === exports.PropertyType.instant) {
12207
- type = exports.SearchParameterType.DATETIME;
12208
- }
12209
- else {
12210
- type = exports.SearchParameterType.DATE;
12211
- }
12212
- break;
12213
- case 'number':
12214
- type = exports.SearchParameterType.NUMBER;
12215
- break;
12216
- case 'quantity':
12217
- type = exports.SearchParameterType.QUANTITY;
12218
- break;
12219
- case 'reference':
12220
- type = exports.SearchParameterType.REFERENCE;
12221
- break;
12222
- case 'token':
12223
- if (propertyType === 'boolean') {
12224
- type = exports.SearchParameterType.BOOLEAN;
12225
- }
12226
- break;
12227
- }
12228
- return type;
12229
- }
12230
- function getExpressionForResourceType(resourceType, expression) {
12231
- const expressions = expression.split(' | ');
12232
- for (const e of expressions) {
12233
- if (isIgnoredExpression(e)) {
12234
- continue;
12235
- }
12236
- const simplified = simplifyExpression(e);
12237
- if (simplified.startsWith(resourceType + '.')) {
12238
- return simplified;
12239
- }
12240
- }
12241
- return undefined;
12242
- }
12243
- function isIgnoredExpression(input) {
12244
- return input.includes(' as Period') || input.includes(' as SampledDate');
12245
- }
12246
- function simplifyExpression(input) {
12247
- let result = input.trim();
12248
- if (result.startsWith('(') && result.endsWith(')')) {
12249
- result = result.substring(1, result.length - 1);
12250
- }
12251
- if (result.includes('[0]')) {
12252
- result = result.replaceAll('[0]', '');
12253
- }
12254
- const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];
12255
- for (const stopString of stopStrings) {
12256
- if (result.includes(stopString)) {
12257
- result = result.substring(0, result.indexOf(stopString));
12258
- }
12259
- }
12260
- return result;
12261
- }
12262
-
12263
- const DEFAULT_SEARCH_COUNT = 20;
12264
- /**
12265
- * Search operators.
12266
- * These operators represent "modifiers" and "prefixes" in FHIR search.
12267
- * See: https://www.hl7.org/fhir/search.html
12268
- */
12269
- exports.Operator = void 0;
12270
- (function (Operator) {
12271
- Operator["EQUALS"] = "eq";
12272
- Operator["NOT_EQUALS"] = "ne";
12273
- // Numbers
12274
- Operator["GREATER_THAN"] = "gt";
12275
- Operator["LESS_THAN"] = "lt";
12276
- Operator["GREATER_THAN_OR_EQUALS"] = "ge";
12277
- Operator["LESS_THAN_OR_EQUALS"] = "le";
12278
- // Dates
12279
- Operator["STARTS_AFTER"] = "sa";
12280
- Operator["ENDS_BEFORE"] = "eb";
12281
- Operator["APPROXIMATELY"] = "ap";
12282
- // String
12283
- Operator["CONTAINS"] = "contains";
12284
- Operator["EXACT"] = "exact";
12285
- // Token
12286
- Operator["TEXT"] = "text";
12287
- Operator["NOT"] = "not";
12288
- Operator["ABOVE"] = "above";
12289
- Operator["BELOW"] = "below";
12290
- Operator["IN"] = "in";
12291
- Operator["NOT_IN"] = "not-in";
12292
- Operator["OF_TYPE"] = "of-type";
12293
- // All
12294
- Operator["MISSING"] = "missing";
12295
- // Reference
12296
- Operator["IDENTIFIER"] = "identifier";
12297
- // _include and _revinclude
12298
- Operator["ITERATE"] = "iterate";
12299
- })(exports.Operator || (exports.Operator = {}));
12300
- /**
12301
- * Parameter names may specify a modifier as a suffix.
12302
- * The modifiers are separated from the parameter name by a colon.
12303
- * See: https://www.hl7.org/fhir/search.html#modifiers
12304
- */
12305
- const MODIFIER_OPERATORS = {
12306
- contains: exports.Operator.CONTAINS,
12307
- exact: exports.Operator.EXACT,
12308
- above: exports.Operator.ABOVE,
12309
- below: exports.Operator.BELOW,
12310
- text: exports.Operator.TEXT,
12311
- not: exports.Operator.NOT,
12312
- in: exports.Operator.IN,
12313
- 'not-in': exports.Operator.NOT_IN,
12314
- 'of-type': exports.Operator.OF_TYPE,
12315
- missing: exports.Operator.MISSING,
12316
- identifier: exports.Operator.IDENTIFIER,
12317
- iterate: exports.Operator.ITERATE,
12318
- };
12319
- /**
12320
- * For the ordered parameter types of number, date, and quantity,
12321
- * a prefix to the parameter value may be used to control the nature
12322
- * of the matching.
12323
- * See: https://www.hl7.org/fhir/search.html#prefix
12324
- */
12325
- const PREFIX_OPERATORS = {
12326
- eq: exports.Operator.EQUALS,
12327
- ne: exports.Operator.NOT_EQUALS,
12328
- lt: exports.Operator.LESS_THAN,
12329
- le: exports.Operator.LESS_THAN_OR_EQUALS,
12330
- gt: exports.Operator.GREATER_THAN,
12331
- ge: exports.Operator.GREATER_THAN_OR_EQUALS,
12332
- sa: exports.Operator.STARTS_AFTER,
12333
- eb: exports.Operator.ENDS_BEFORE,
12334
- ap: exports.Operator.APPROXIMATELY,
12335
- };
12336
- /**
12337
- * Parses a search URL into a search request.
12338
- * @param resourceType The FHIR resource type.
12339
- * @param query The collection of query string parameters.
12340
- * @returns A parsed SearchRequest.
12341
- */
12342
- function parseSearchRequest(resourceType, query) {
12343
- const queryArray = [];
12344
- for (const [key, value] of Object.entries(query)) {
12345
- if (Array.isArray(value)) {
12346
- for (let i = 0; i < value.length; i++) {
12347
- queryArray.push([key, value[i]]);
12348
- }
12349
- }
12350
- else {
12351
- queryArray.push([key, value || '']);
12352
- }
12353
- }
12354
- return parseSearchImpl(resourceType, queryArray);
12355
- }
12356
- /**
12357
- * Parses a search URL into a search request.
12358
- * @param url The search URL.
12359
- * @returns A parsed SearchRequest.
12360
- */
12361
- function parseSearchUrl(url) {
12362
- const resourceType = url.pathname.split('/').filter(Boolean).pop();
12363
- return parseSearchImpl(resourceType, url.searchParams.entries());
12364
- }
12365
- /**
12366
- * Parses a URL string into a SearchRequest.
12367
- * @param url The URL to parse.
12368
- * @returns Parsed search definition.
12369
- */
12370
- function parseSearchDefinition(url) {
12371
- return parseSearchUrl(new URL(url, 'https://example.com/'));
12372
- }
12373
- function parseSearchImpl(resourceType, query) {
12374
- const searchRequest = {
12375
- resourceType,
12376
- };
12377
- for (const [key, value] of query) {
12378
- parseKeyValue(searchRequest, key, value);
12379
- }
12380
- return searchRequest;
12381
- }
12382
- function parseKeyValue(searchRequest, key, value) {
12383
- let code;
12384
- let modifier;
12385
- const colonIndex = key.indexOf(':');
12386
- if (colonIndex >= 0) {
12387
- code = key.substring(0, colonIndex);
12388
- modifier = key.substring(colonIndex + 1);
12389
- }
12390
- else {
12391
- code = key;
12392
- modifier = '';
12393
- }
12394
- switch (code) {
12395
- case '_sort':
12396
- parseSortRule(searchRequest, value);
12397
- break;
12398
- case '_count':
12399
- searchRequest.count = parseInt(value);
12400
- break;
12401
- case '_offset':
12402
- searchRequest.offset = parseInt(value);
12403
- break;
12404
- case '_total':
12405
- searchRequest.total = value;
12406
- break;
12407
- case '_summary':
12408
- searchRequest.total = 'estimate';
12409
- searchRequest.count = 0;
12410
- break;
12411
- case '_include': {
12412
- const target = parseIncludeTarget(value);
12413
- if (modifier === 'iterate') {
12414
- target.modifier = exports.Operator.ITERATE;
12415
- }
12416
- if (searchRequest.include) {
12417
- searchRequest.include.push(target);
12418
- }
12419
- else {
12420
- searchRequest.include = [target];
12421
- }
12422
- break;
12638
+ let baseType = resourceType;
12639
+ let elementDefinition = undefined;
12640
+ let propertyType = undefined;
12641
+ let array = false;
12642
+ for (let i = 1; i < expression.length; i++) {
12643
+ let propertyName = expression[i];
12644
+ let hasArrayIndex = false;
12645
+ const arrayIndexMatch = /\[\d+\]$/.exec(propertyName);
12646
+ if (arrayIndexMatch) {
12647
+ propertyName = propertyName.substring(0, propertyName.length - arrayIndexMatch[0].length);
12648
+ hasArrayIndex = true;
12423
12649
  }
12424
- case '_revinclude': {
12425
- const target = parseIncludeTarget(value);
12426
- if (modifier === 'iterate') {
12427
- target.modifier = exports.Operator.ITERATE;
12428
- }
12429
- if (searchRequest.revInclude) {
12430
- searchRequest.revInclude.push(target);
12431
- }
12432
- else {
12433
- searchRequest.revInclude = [target];
12434
- }
12435
- break;
12650
+ elementDefinition = getElementDefinition(baseType, propertyName);
12651
+ if (!elementDefinition) {
12652
+ throw new Error(`Element definition not found for ${resourceType} ${searchParam.code}`);
12436
12653
  }
12437
- case '_fields':
12438
- searchRequest.fields = value.split(',');
12439
- break;
12440
- default: {
12441
- const param = globalSchema.types[searchRequest.resourceType]?.searchParams?.[code];
12442
- if (param) {
12443
- parseParameter(searchRequest, param, modifier, value);
12654
+ if (elementDefinition.max !== '0' && elementDefinition.max !== '1' && !hasArrayIndex) {
12655
+ array = true;
12656
+ }
12657
+ // "code" is only missing when using "contentReference"
12658
+ // "contentReference" is handled above in "getElementDefinition"
12659
+ propertyType = elementDefinition.type?.[0].code;
12660
+ if (i < expression.length - 1) {
12661
+ if (isBackboneElement(propertyType)) {
12662
+ baseType = buildTypeName(elementDefinition.path?.split('.'));
12444
12663
  }
12445
12664
  else {
12446
- parseUnknownParameter(searchRequest, code, modifier, value);
12665
+ baseType = propertyType;
12447
12666
  }
12448
12667
  }
12449
12668
  }
12669
+ const type = getSearchParameterType(searchParam, propertyType);
12670
+ const result = { columnName, type, elementDefinition, array };
12671
+ setSearchParamterDetails(resourceType, code, result);
12672
+ return result;
12450
12673
  }
12451
- function parseSortRule(searchRequest, value) {
12452
- for (const field of value.split(',')) {
12453
- let code;
12454
- let descending = false;
12455
- if (field.startsWith('-')) {
12456
- code = field.substring(1);
12457
- descending = true;
12458
- }
12459
- else {
12460
- code = field;
12461
- }
12462
- if (!searchRequest.sortRules) {
12463
- searchRequest.sortRules = [];
12464
- }
12465
- searchRequest.sortRules.push({ code, descending });
12466
- }
12674
+ function isBackboneElement(propertyType) {
12675
+ return propertyType === 'Element' || propertyType === 'BackboneElement';
12467
12676
  }
12468
- function parseParameter(searchRequest, searchParam, modifier, value) {
12469
- if (modifier === 'missing') {
12470
- addFilter(searchRequest, {
12471
- code: searchParam.code,
12472
- operator: exports.Operator.MISSING,
12473
- value,
12474
- });
12475
- return;
12476
- }
12677
+ /**
12678
+ * Converts a hyphen-delimited code to camelCase string.
12679
+ * @param code The search parameter code.
12680
+ * @returns The SQL column name.
12681
+ */
12682
+ function convertCodeToColumnName(code) {
12683
+ return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
12684
+ }
12685
+ function getSearchParameterType(searchParam, propertyType) {
12686
+ let type = exports.SearchParameterType.TEXT;
12477
12687
  switch (searchParam.type) {
12478
- case 'number':
12479
12688
  case 'date':
12480
- parsePrefixType(searchRequest, searchParam, value);
12689
+ if (propertyType === exports.PropertyType.date) {
12690
+ type = exports.SearchParameterType.DATE;
12691
+ }
12692
+ else {
12693
+ type = exports.SearchParameterType.DATETIME;
12694
+ }
12481
12695
  break;
12482
- case 'reference':
12483
- case 'string':
12484
- case 'token':
12485
- case 'uri':
12486
- parseModifierType(searchRequest, searchParam, modifier, value);
12696
+ case 'number':
12697
+ type = exports.SearchParameterType.NUMBER;
12487
12698
  break;
12488
12699
  case 'quantity':
12489
- parseQuantity(searchRequest, searchParam, value);
12700
+ type = exports.SearchParameterType.QUANTITY;
12490
12701
  break;
12491
- }
12492
- }
12493
- function parsePrefixType(searchRequest, param, input) {
12494
- const { operator, value } = parsePrefix(input);
12495
- addFilter(searchRequest, {
12496
- code: param.code,
12497
- operator,
12498
- value,
12499
- });
12500
- }
12501
- function parseModifierType(searchRequest, param, modifier, value) {
12502
- addFilter(searchRequest, {
12503
- code: param.code,
12504
- operator: parseModifier(modifier),
12505
- value,
12506
- });
12507
- }
12508
- function parseQuantity(searchRequest, param, input) {
12509
- const [prefixNumber, unitSystem, unitCode] = input.split('|');
12510
- const { operator, value } = parsePrefix(prefixNumber);
12511
- addFilter(searchRequest, {
12512
- code: param.code,
12513
- operator,
12514
- value,
12515
- unitSystem,
12516
- unitCode,
12517
- });
12518
- }
12519
- function parseUnknownParameter(searchRequest, code, modifier, value) {
12520
- let operator = exports.Operator.EQUALS;
12521
- if (modifier) {
12522
- operator = modifier;
12523
- }
12524
- else if (value.length >= 2) {
12525
- const prefix = value.substring(0, 2);
12526
- if (prefix in PREFIX_OPERATORS) {
12527
- if (value.length === 2 || value.at(2)?.match(/\d/)) {
12528
- operator = prefix;
12529
- value = value.substring(prefix.length);
12702
+ case 'reference':
12703
+ if (propertyType === exports.PropertyType.canonical) {
12704
+ type = exports.SearchParameterType.CANONICAL;
12530
12705
  }
12531
- }
12532
- }
12533
- addFilter(searchRequest, {
12534
- code,
12535
- operator,
12536
- value,
12537
- });
12538
- }
12539
- function parsePrefix(input) {
12540
- const prefix = input.substring(0, 2);
12541
- const prefixOperator = PREFIX_OPERATORS[prefix];
12542
- if (prefixOperator) {
12543
- return { operator: prefixOperator, value: input.substring(2) };
12706
+ else {
12707
+ type = exports.SearchParameterType.REFERENCE;
12708
+ }
12709
+ break;
12710
+ case 'token':
12711
+ if (propertyType === 'boolean') {
12712
+ type = exports.SearchParameterType.BOOLEAN;
12713
+ }
12714
+ break;
12544
12715
  }
12545
- return { operator: exports.Operator.EQUALS, value: input };
12546
- }
12547
- function parseModifier(modifier) {
12548
- return MODIFIER_OPERATORS[modifier] || exports.Operator.EQUALS;
12716
+ return type;
12549
12717
  }
12550
- function parseIncludeTarget(input) {
12551
- const parts = input.split(':');
12552
- parts.forEach((p) => {
12553
- if (p === '*') {
12554
- throw new OperationOutcomeError(badRequest(`'*' is not supported as a value for search inclusion parameters`));
12718
+ function getExpressionForResourceType(resourceType, expression) {
12719
+ const expressions = expression.split(' | ');
12720
+ for (const e of expressions) {
12721
+ if (isIgnoredExpression(e)) {
12722
+ continue;
12723
+ }
12724
+ const simplified = simplifyExpression(e);
12725
+ if (simplified.startsWith(resourceType + '.')) {
12726
+ return simplified;
12555
12727
  }
12556
- });
12557
- if (parts.length === 1) {
12558
- // Full wildcard, not currently supported
12559
- throw new OperationOutcomeError(badRequest(`Invalid include value '${input}': must be of the form ResourceType:search-parameter`));
12560
- }
12561
- else if (parts.length === 2) {
12562
- return {
12563
- resourceType: parts[0],
12564
- searchParam: parts[1],
12565
- };
12566
- }
12567
- else if (parts.length === 3) {
12568
- return {
12569
- resourceType: parts[0],
12570
- searchParam: parts[1],
12571
- targetType: parts[2],
12572
- };
12573
- }
12574
- else {
12575
- throw new OperationOutcomeError(badRequest(`Invalid include value '${input}'`));
12576
12728
  }
12729
+ return undefined;
12577
12730
  }
12578
- function addFilter(searchRequest, filter) {
12579
- if (searchRequest.filters) {
12580
- searchRequest.filters.push(filter);
12581
- }
12582
- else {
12583
- searchRequest.filters = [filter];
12584
- }
12731
+ function isIgnoredExpression(input) {
12732
+ return input.includes(' as Period') || input.includes(' as SampledDate');
12585
12733
  }
12586
- /**
12587
- * Formats a search definition object into a query string.
12588
- * Note: The return value does not include the resource type.
12589
- * @param {!SearchRequest} definition The search definition.
12590
- * @returns Formatted URL.
12591
- */
12592
- function formatSearchQuery(definition) {
12593
- const params = [];
12594
- if (definition.fields) {
12595
- params.push('_fields=' + definition.fields.join(','));
12596
- }
12597
- if (definition.filters) {
12598
- definition.filters.forEach((filter) => params.push(formatFilter(filter)));
12599
- }
12600
- if (definition.sortRules && definition.sortRules.length > 0) {
12601
- params.push(formatSortRules(definition.sortRules));
12602
- }
12603
- if (definition.offset !== undefined) {
12604
- params.push('_offset=' + definition.offset);
12605
- }
12606
- if (definition.count !== undefined) {
12607
- params.push('_count=' + definition.count);
12608
- }
12609
- if (definition.total !== undefined) {
12610
- params.push('_total=' + definition.total);
12734
+ function simplifyExpression(input) {
12735
+ let result = input.trim();
12736
+ if (result.startsWith('(') && result.endsWith(')')) {
12737
+ result = result.substring(1, result.length - 1);
12611
12738
  }
12612
- if (params.length === 0) {
12613
- return '';
12739
+ const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];
12740
+ for (const stopString of stopStrings) {
12741
+ if (result.includes(stopString)) {
12742
+ result = result.substring(0, result.indexOf(stopString));
12743
+ }
12614
12744
  }
12615
- params.sort();
12616
- return '?' + params.join('&');
12617
- }
12618
- function formatFilter(filter) {
12619
- const modifier = filter.operator in MODIFIER_OPERATORS ? ':' + filter.operator : '';
12620
- const prefix = filter.operator !== exports.Operator.EQUALS && filter.operator in PREFIX_OPERATORS ? filter.operator : '';
12621
- return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
12622
- }
12623
- function formatSortRules(sortRules) {
12624
- return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
12745
+ return result;
12625
12746
  }
12626
12747
 
12627
12748
  /**
@@ -12779,6 +12900,29 @@
12779
12900
  return operator === exports.Operator.NOT_EQUALS || operator === exports.Operator.NOT;
12780
12901
  }
12781
12902
 
12903
+ /**
12904
+ * Reads data from a Readable stream and returns a Promise that resolves with a Buffer containing all the data.
12905
+ * @param stream - The Readable stream to read from.
12906
+ * @returns A Promise that resolves with a Buffer containing all the data from the Readable stream.
12907
+ */
12908
+ function streamToBuffer(stream) {
12909
+ const chunks = [];
12910
+ return new Promise((resolve, reject) => {
12911
+ stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
12912
+ stream.on('error', (err) => {
12913
+ console.error(err.message);
12914
+ stream.destroy();
12915
+ reject(err);
12916
+ });
12917
+ stream.on('end', () => {
12918
+ resolve(Buffer.concat(chunks));
12919
+ });
12920
+ stream.on('close', () => {
12921
+ stream.destroy();
12922
+ });
12923
+ });
12924
+ }
12925
+
12782
12926
  exports.AndAtom = AndAtom;
12783
12927
  exports.ArithemticOperatorAtom = ArithemticOperatorAtom;
12784
12928
  exports.AsAtom = AsAtom;
@@ -12852,6 +12996,7 @@
12852
12996
  exports.fhirPathNot = fhirPathNot;
12853
12997
  exports.findObservationInterval = findObservationInterval;
12854
12998
  exports.findObservationReferenceRange = findObservationReferenceRange;
12999
+ exports.findResourceByCode = findResourceByCode;
12855
13000
  exports.forbidden = forbidden;
12856
13001
  exports.formatAddress = formatAddress;
12857
13002
  exports.formatCodeableConcept = formatCodeableConcept;
@@ -12922,6 +13067,7 @@
12922
13067
  exports.operationOutcomeToString = operationOutcomeToString;
12923
13068
  exports.parseFhirPath = parseFhirPath;
12924
13069
  exports.parseFilterParameter = parseFilterParameter;
13070
+ exports.parseHl7Date = parseHl7Date;
12925
13071
  exports.parseJWTPayload = parseJWTPayload;
12926
13072
  exports.parseMappingLanguage = parseMappingLanguage;
12927
13073
  exports.parseSearchDefinition = parseSearchDefinition;
@@ -12936,6 +13082,7 @@
12936
13082
  exports.removeDuplicates = removeDuplicates;
12937
13083
  exports.resolveId = resolveId;
12938
13084
  exports.setCodeBySystem = setCodeBySystem;
13085
+ exports.streamToBuffer = streamToBuffer;
12939
13086
  exports.stringify = stringify;
12940
13087
  exports.toJsBoolean = toJsBoolean;
12941
13088
  exports.toTypedValue = toTypedValue;