@medplum/core 2.0.17 → 2.0.19

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.
@@ -1555,7 +1567,7 @@
1555
1567
  *
1556
1568
  * When using MedplumClient in the browser, it will be backed by browser localStorage.
1557
1569
  *
1558
- * When Using MedplumClient in the server, it will be backed by the MemoryStorage class.
1570
+ * When Using MedplumClient in the server, it will be backed by the MemoryStorage class. For example, the Medplum CLI uses `FileSystemStorage`.
1559
1571
  */
1560
1572
  class ClientStorage {
1561
1573
  constructor() {
@@ -6404,7 +6416,8 @@
6404
6416
  const globalSchema = baseSchema;
6405
6417
 
6406
6418
  // PKCE auth based on:
6407
- const MEDPLUM_VERSION = "2.0.17-5c5ebbda";
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.19-40e6e27d" ;
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
@@ -7906,6 +7919,26 @@
7906
7919
  const response = await this.fetch(url.toString(), options);
7907
7920
  return response.blob();
7908
7921
  }
7922
+ /**
7923
+ * Upload media to the server and create a Media instance for the uploaded content.
7924
+ * @param contents The contents of the media file, as a string, Uint8Array, File, or Blob.
7925
+ * @param contentType The media type of the content
7926
+ * @param filename The name of the file to be uploaded, or undefined if not applicable
7927
+ * @param additionalFields Additional fields for Media
7928
+ * @returns Promise that resolves to the created Media
7929
+ */
7930
+ async uploadMedia(contents, contentType, filename, additionalFields) {
7931
+ const binary = await this.createBinary(contents, filename, contentType);
7932
+ return this.createResource({
7933
+ ...additionalFields,
7934
+ resourceType: 'Media',
7935
+ content: {
7936
+ contentType: contentType,
7937
+ url: 'Binary/' + binary.id,
7938
+ title: filename,
7939
+ },
7940
+ });
7941
+ }
7909
7942
  //
7910
7943
  // Private helpers
7911
7944
  //
@@ -7980,6 +8013,12 @@
7980
8013
  // No content or change
7981
8014
  return undefined;
7982
8015
  }
8016
+ if (response.status === 404) {
8017
+ const contentType = response.headers.get('content-type');
8018
+ if (!contentType?.includes('application/fhir+json')) {
8019
+ throw new OperationOutcomeError(notFound);
8020
+ }
8021
+ }
7983
8022
  let obj = undefined;
7984
8023
  try {
7985
8024
  obj = await response.json();
@@ -8204,6 +8243,14 @@
8204
8243
  }
8205
8244
  /**
8206
8245
  * Starts a new OAuth2 client credentials flow.
8246
+ *
8247
+ * ```typescript
8248
+ * await medplum.startClientLogin(process.env.MEDPLUM_CLIENT_ID, process.env.MEDPLUM_CLIENT_SECRET)
8249
+ * // Example Search
8250
+ * await medplum.searchResources('Patient')
8251
+ * ```
8252
+ *
8253
+ *
8207
8254
  * See: https://datatracker.ietf.org/doc/html/rfc6749#section-4.4
8208
8255
  * @category Authentication
8209
8256
  * @param clientId The client ID.
@@ -8221,6 +8268,13 @@
8221
8268
  }
8222
8269
  /**
8223
8270
  * Sets the client ID and secret for basic auth.
8271
+ *
8272
+ * ```typescript
8273
+ * medplum.setBasicAuth(process.env.MEDPLUM_CLIENT_ID, process.env.MEDPLUM_CLIENT_SECRET)
8274
+ * // Example Search
8275
+ * await medplum.searchResources('Patient')
8276
+ * ```
8277
+ *
8224
8278
  * @category Authentication
8225
8279
  * @param clientId The client ID.
8226
8280
  * @param clientSecret The client secret.
@@ -11376,156 +11430,571 @@
11376
11430
  return new StructureMapParser(parser).parse();
11377
11431
  }
11378
11432
 
11379
- const MAPPING_LANGUAGE_OPERATORS = [...FHIRPATH_OPERATORS, 'eq', 'ne', 'co'];
11380
- function tokenize(str) {
11381
- return new Tokenizer(str, FHIRPATH_KEYWORDS, MAPPING_LANGUAGE_OPERATORS, {
11382
- dateTimeLiterals: true,
11383
- symbolRegex: /[^\s\])]/,
11384
- }).tokenize();
11385
- }
11386
-
11387
- // See: https://hl7.org/fhir/search_filter.html
11433
+ const DEFAULT_SEARCH_COUNT = 20;
11388
11434
  /**
11389
- * The FhirFilterComparison class represents a comparison expression.
11435
+ * Search operators.
11436
+ * These operators represent "modifiers" and "prefixes" in FHIR search.
11437
+ * See: https://www.hl7.org/fhir/search.html
11390
11438
  */
11391
- class FhirFilterComparison {
11392
- constructor(path, operator, value) {
11393
- this.path = path;
11394
- this.operator = operator;
11395
- this.value = value;
11396
- }
11397
- }
11439
+ exports.Operator = void 0;
11440
+ (function (Operator) {
11441
+ Operator["EQUALS"] = "eq";
11442
+ Operator["NOT_EQUALS"] = "ne";
11443
+ // Numbers
11444
+ Operator["GREATER_THAN"] = "gt";
11445
+ Operator["LESS_THAN"] = "lt";
11446
+ Operator["GREATER_THAN_OR_EQUALS"] = "ge";
11447
+ Operator["LESS_THAN_OR_EQUALS"] = "le";
11448
+ // Dates
11449
+ Operator["STARTS_AFTER"] = "sa";
11450
+ Operator["ENDS_BEFORE"] = "eb";
11451
+ Operator["APPROXIMATELY"] = "ap";
11452
+ // String
11453
+ Operator["CONTAINS"] = "contains";
11454
+ Operator["EXACT"] = "exact";
11455
+ // Token
11456
+ Operator["TEXT"] = "text";
11457
+ Operator["NOT"] = "not";
11458
+ Operator["ABOVE"] = "above";
11459
+ Operator["BELOW"] = "below";
11460
+ Operator["IN"] = "in";
11461
+ Operator["NOT_IN"] = "not-in";
11462
+ Operator["OF_TYPE"] = "of-type";
11463
+ // All
11464
+ Operator["MISSING"] = "missing";
11465
+ // Reference
11466
+ Operator["IDENTIFIER"] = "identifier";
11467
+ // _include and _revinclude
11468
+ Operator["ITERATE"] = "iterate";
11469
+ })(exports.Operator || (exports.Operator = {}));
11398
11470
  /**
11399
- * The FhirFilterNegation class represents a negation expression.
11400
- * It contains a single child expression.
11471
+ * Parameter names may specify a modifier as a suffix.
11472
+ * The modifiers are separated from the parameter name by a colon.
11473
+ * See: https://www.hl7.org/fhir/search.html#modifiers
11401
11474
  */
11402
- class FhirFilterNegation {
11403
- constructor(child) {
11404
- this.child = child;
11405
- }
11406
- }
11475
+ const MODIFIER_OPERATORS = {
11476
+ contains: exports.Operator.CONTAINS,
11477
+ exact: exports.Operator.EXACT,
11478
+ above: exports.Operator.ABOVE,
11479
+ below: exports.Operator.BELOW,
11480
+ text: exports.Operator.TEXT,
11481
+ not: exports.Operator.NOT,
11482
+ in: exports.Operator.IN,
11483
+ 'not-in': exports.Operator.NOT_IN,
11484
+ 'of-type': exports.Operator.OF_TYPE,
11485
+ missing: exports.Operator.MISSING,
11486
+ identifier: exports.Operator.IDENTIFIER,
11487
+ iterate: exports.Operator.ITERATE,
11488
+ };
11407
11489
  /**
11408
- * The FhirFilterConnective class represents a connective expression.
11409
- * It contains a list of child expressions.
11490
+ * For the ordered parameter types of number, date, and quantity,
11491
+ * a prefix to the parameter value may be used to control the nature
11492
+ * of the matching.
11493
+ * See: https://www.hl7.org/fhir/search.html#prefix
11410
11494
  */
11411
- class FhirFilterConnective {
11412
- constructor(keyword, left, right) {
11413
- this.keyword = keyword;
11414
- this.left = left;
11415
- this.right = right;
11416
- }
11417
- }
11418
-
11419
- class FilterParameterParser {
11420
- constructor(parser) {
11421
- this.parser = parser;
11422
- }
11423
- parse() {
11424
- let result;
11425
- if (this.parser.peek()?.value === '(') {
11426
- this.parser.consume('(');
11427
- result = this.parse();
11428
- this.parser.consume(')');
11429
- }
11430
- else if (this.parser.peek()?.value === 'not') {
11431
- this.parser.consume('Symbol', 'not');
11432
- this.parser.consume('(');
11433
- result = new FhirFilterNegation(this.parse());
11434
- this.parser.consume(')');
11495
+ const PREFIX_OPERATORS = {
11496
+ eq: exports.Operator.EQUALS,
11497
+ ne: exports.Operator.NOT_EQUALS,
11498
+ lt: exports.Operator.LESS_THAN,
11499
+ le: exports.Operator.LESS_THAN_OR_EQUALS,
11500
+ gt: exports.Operator.GREATER_THAN,
11501
+ ge: exports.Operator.GREATER_THAN_OR_EQUALS,
11502
+ sa: exports.Operator.STARTS_AFTER,
11503
+ eb: exports.Operator.ENDS_BEFORE,
11504
+ ap: exports.Operator.APPROXIMATELY,
11505
+ };
11506
+ /**
11507
+ * Parses a search URL into a search request.
11508
+ * @param resourceType The FHIR resource type.
11509
+ * @param query The collection of query string parameters.
11510
+ * @returns A parsed SearchRequest.
11511
+ */
11512
+ function parseSearchRequest(resourceType, query) {
11513
+ const queryArray = [];
11514
+ for (const [key, value] of Object.entries(query)) {
11515
+ if (Array.isArray(value)) {
11516
+ for (let i = 0; i < value.length; i++) {
11517
+ queryArray.push([key, value[i]]);
11518
+ }
11435
11519
  }
11436
11520
  else {
11437
- result = new FhirFilterComparison(this.parser.consume('Symbol').value, this.parser.consume('Symbol').value, this.parser.consume().value);
11438
- }
11439
- const next = this.parser.peek()?.value;
11440
- if (next === 'and' || next === 'or') {
11441
- this.parser.consume('Symbol', next);
11442
- return new FhirFilterConnective(next, result, this.parse());
11521
+ queryArray.push([key, value || '']);
11443
11522
  }
11444
- return result;
11445
11523
  }
11524
+ return parseSearchImpl(resourceType, queryArray);
11446
11525
  }
11447
- const fhirPathParserBuilder = initFhirPathParserBuilder();
11448
11526
  /**
11449
- * Parses a FHIR _filter parameter expression into an AST.
11450
- * @param input The FHIR _filter parameter expression.
11451
- * @returns The AST representing the filters.
11527
+ * Parses a search URL into a search request.
11528
+ * @param url The search URL.
11529
+ * @returns A parsed SearchRequest.
11452
11530
  */
11453
- function parseFilterParameter(input) {
11454
- const parser = fhirPathParserBuilder.construct(tokenize(input));
11455
- parser.removeComments();
11456
- return new FilterParameterParser(parser).parse();
11531
+ function parseSearchUrl(url) {
11532
+ const resourceType = url.pathname.split('/').filter(Boolean).pop();
11533
+ return parseSearchImpl(resourceType, url.searchParams.entries());
11457
11534
  }
11458
-
11459
11535
  /**
11460
- * The Hl7Context class represents the parsing context for an HL7 message.
11461
- *
11462
- * MSH-1:
11463
- * https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.1
11464
- *
11465
- * MSH-2:
11466
- * https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.2
11467
- *
11468
- * See this tutorial on MSH, and why it's a bad idea to use anything other than the default values:
11469
- * https://www.hl7soup.com/HL7TutorialMSH.html
11536
+ * Parses a URL string into a SearchRequest.
11537
+ * @param url The URL to parse.
11538
+ * @returns Parsed search definition.
11470
11539
  */
11471
- class Hl7Context {
11472
- constructor(segmentSeparator = '\r', fieldSeparator = '|', componentSeparator = '^', repetitionSeparator = '~', escapeCharacter = '\\', subcomponentSeparator = '&') {
11473
- this.segmentSeparator = segmentSeparator;
11474
- this.fieldSeparator = fieldSeparator;
11475
- this.componentSeparator = componentSeparator;
11476
- this.repetitionSeparator = repetitionSeparator;
11477
- this.escapeCharacter = escapeCharacter;
11478
- this.subcomponentSeparator = subcomponentSeparator;
11479
- }
11480
- /**
11481
- * Returns the MSH-2 field value based on the configured separators.
11482
- * @returns The HL7 MSH-2 field value.
11483
- */
11484
- getMsh2() {
11485
- return (this.fieldSeparator +
11486
- this.componentSeparator +
11487
- this.repetitionSeparator +
11488
- this.escapeCharacter +
11489
- this.subcomponentSeparator);
11490
- }
11540
+ function parseSearchDefinition(url) {
11541
+ return parseSearchUrl(new URL(url, 'https://example.com/'));
11491
11542
  }
11492
- /**
11493
- * The Hl7Message class represents one HL7 message.
11494
- * A message is a collection of segments.
11495
- */
11496
- class Hl7Message {
11497
- /**
11498
- * Creates a new HL7 message.
11499
- * @param segments The HL7 segments.
11500
- * @param context Optional HL7 parsing context.
11501
- */
11502
- constructor(segments, context = new Hl7Context()) {
11503
- this.context = context;
11504
- this.segments = segments;
11543
+ function parseSearchImpl(resourceType, query) {
11544
+ const searchRequest = {
11545
+ resourceType,
11546
+ };
11547
+ for (const [key, value] of query) {
11548
+ parseKeyValue(searchRequest, key, value);
11505
11549
  }
11506
- /**
11507
- * Returns an HL7 segment by index or by name.
11508
- * @param index The HL7 segment index or name.
11509
- * @returns The HL7 segment if found; otherwise, undefined.
11510
- */
11511
- get(index) {
11512
- if (typeof index === 'number') {
11513
- return this.segments[index];
11514
- }
11515
- return this.segments.find((s) => s.name === index);
11550
+ return searchRequest;
11551
+ }
11552
+ function parseKeyValue(searchRequest, key, value) {
11553
+ let code;
11554
+ let modifier;
11555
+ const colonIndex = key.indexOf(':');
11556
+ if (colonIndex >= 0) {
11557
+ code = key.substring(0, colonIndex);
11558
+ modifier = key.substring(colonIndex + 1);
11516
11559
  }
11517
- /**
11518
- * Returns all HL7 segments of a given name.
11519
- * @param name The HL7 segment name.
11520
- * @returns An array of HL7 segments with the specified name.
11521
- */
11522
- getAll(name) {
11523
- return this.segments.filter((s) => s.name === name);
11560
+ else {
11561
+ code = key;
11562
+ modifier = '';
11524
11563
  }
11525
- /**
11526
- * Returns the HL7 message as a string.
11527
- * @returns The HL7 message as a string.
11528
- */
11564
+ switch (code) {
11565
+ case '_sort':
11566
+ parseSortRule(searchRequest, value);
11567
+ break;
11568
+ case '_count':
11569
+ searchRequest.count = parseInt(value);
11570
+ break;
11571
+ case '_offset':
11572
+ searchRequest.offset = parseInt(value);
11573
+ break;
11574
+ case '_total':
11575
+ searchRequest.total = value;
11576
+ break;
11577
+ case '_summary':
11578
+ searchRequest.total = 'estimate';
11579
+ searchRequest.count = 0;
11580
+ break;
11581
+ case '_include': {
11582
+ const target = parseIncludeTarget(value);
11583
+ if (modifier === 'iterate') {
11584
+ target.modifier = exports.Operator.ITERATE;
11585
+ }
11586
+ if (searchRequest.include) {
11587
+ searchRequest.include.push(target);
11588
+ }
11589
+ else {
11590
+ searchRequest.include = [target];
11591
+ }
11592
+ break;
11593
+ }
11594
+ case '_revinclude': {
11595
+ const target = parseIncludeTarget(value);
11596
+ if (modifier === 'iterate') {
11597
+ target.modifier = exports.Operator.ITERATE;
11598
+ }
11599
+ if (searchRequest.revInclude) {
11600
+ searchRequest.revInclude.push(target);
11601
+ }
11602
+ else {
11603
+ searchRequest.revInclude = [target];
11604
+ }
11605
+ break;
11606
+ }
11607
+ case '_fields':
11608
+ searchRequest.fields = value.split(',');
11609
+ break;
11610
+ default: {
11611
+ const param = globalSchema.types[searchRequest.resourceType]?.searchParams?.[code];
11612
+ if (param) {
11613
+ parseParameter(searchRequest, param, modifier, value);
11614
+ }
11615
+ else {
11616
+ parseUnknownParameter(searchRequest, code, modifier, value);
11617
+ }
11618
+ }
11619
+ }
11620
+ }
11621
+ function parseSortRule(searchRequest, value) {
11622
+ for (const field of value.split(',')) {
11623
+ let code;
11624
+ let descending = false;
11625
+ if (field.startsWith('-')) {
11626
+ code = field.substring(1);
11627
+ descending = true;
11628
+ }
11629
+ else {
11630
+ code = field;
11631
+ }
11632
+ if (!searchRequest.sortRules) {
11633
+ searchRequest.sortRules = [];
11634
+ }
11635
+ searchRequest.sortRules.push({ code, descending });
11636
+ }
11637
+ }
11638
+ function parseParameter(searchRequest, searchParam, modifier, value) {
11639
+ if (modifier === 'missing') {
11640
+ addFilter(searchRequest, {
11641
+ code: searchParam.code,
11642
+ operator: exports.Operator.MISSING,
11643
+ value,
11644
+ });
11645
+ return;
11646
+ }
11647
+ switch (searchParam.type) {
11648
+ case 'number':
11649
+ case 'date':
11650
+ parsePrefixType(searchRequest, searchParam, value);
11651
+ break;
11652
+ case 'reference':
11653
+ case 'string':
11654
+ case 'token':
11655
+ case 'uri':
11656
+ parseModifierType(searchRequest, searchParam, modifier, value);
11657
+ break;
11658
+ case 'quantity':
11659
+ parseQuantity(searchRequest, searchParam, value);
11660
+ break;
11661
+ }
11662
+ }
11663
+ function parsePrefixType(searchRequest, param, input) {
11664
+ const { operator, value } = parsePrefix(input);
11665
+ addFilter(searchRequest, {
11666
+ code: param.code,
11667
+ operator,
11668
+ value,
11669
+ });
11670
+ }
11671
+ function parseModifierType(searchRequest, param, modifier, value) {
11672
+ addFilter(searchRequest, {
11673
+ code: param.code,
11674
+ operator: parseModifier(modifier),
11675
+ value,
11676
+ });
11677
+ }
11678
+ function parseQuantity(searchRequest, param, input) {
11679
+ const [prefixNumber, unitSystem, unitCode] = input.split('|');
11680
+ const { operator, value } = parsePrefix(prefixNumber);
11681
+ addFilter(searchRequest, {
11682
+ code: param.code,
11683
+ operator,
11684
+ value,
11685
+ unitSystem,
11686
+ unitCode,
11687
+ });
11688
+ }
11689
+ function parseUnknownParameter(searchRequest, code, modifier, value) {
11690
+ let operator = exports.Operator.EQUALS;
11691
+ if (modifier) {
11692
+ operator = modifier;
11693
+ }
11694
+ else if (value.length >= 2) {
11695
+ const prefix = value.substring(0, 2);
11696
+ if (prefix in PREFIX_OPERATORS) {
11697
+ if (value.length === 2 || value.at(2)?.match(/\d/)) {
11698
+ operator = prefix;
11699
+ value = value.substring(prefix.length);
11700
+ }
11701
+ }
11702
+ }
11703
+ addFilter(searchRequest, {
11704
+ code,
11705
+ operator,
11706
+ value,
11707
+ });
11708
+ }
11709
+ function parsePrefix(input) {
11710
+ const prefix = input.substring(0, 2);
11711
+ const prefixOperator = PREFIX_OPERATORS[prefix];
11712
+ if (prefixOperator) {
11713
+ return { operator: prefixOperator, value: input.substring(2) };
11714
+ }
11715
+ return { operator: exports.Operator.EQUALS, value: input };
11716
+ }
11717
+ function parseModifier(modifier) {
11718
+ return MODIFIER_OPERATORS[modifier] || exports.Operator.EQUALS;
11719
+ }
11720
+ function parseIncludeTarget(input) {
11721
+ const parts = input.split(':');
11722
+ parts.forEach((p) => {
11723
+ if (p === '*') {
11724
+ throw new OperationOutcomeError(badRequest(`'*' is not supported as a value for search inclusion parameters`));
11725
+ }
11726
+ });
11727
+ if (parts.length === 1) {
11728
+ // Full wildcard, not currently supported
11729
+ throw new OperationOutcomeError(badRequest(`Invalid include value '${input}': must be of the form ResourceType:search-parameter`));
11730
+ }
11731
+ else if (parts.length === 2) {
11732
+ return {
11733
+ resourceType: parts[0],
11734
+ searchParam: parts[1],
11735
+ };
11736
+ }
11737
+ else if (parts.length === 3) {
11738
+ return {
11739
+ resourceType: parts[0],
11740
+ searchParam: parts[1],
11741
+ targetType: parts[2],
11742
+ };
11743
+ }
11744
+ else {
11745
+ throw new OperationOutcomeError(badRequest(`Invalid include value '${input}'`));
11746
+ }
11747
+ }
11748
+ function addFilter(searchRequest, filter) {
11749
+ if (searchRequest.filters) {
11750
+ searchRequest.filters.push(filter);
11751
+ }
11752
+ else {
11753
+ searchRequest.filters = [filter];
11754
+ }
11755
+ }
11756
+ /**
11757
+ * Formats a search definition object into a query string.
11758
+ * Note: The return value does not include the resource type.
11759
+ * @param {!SearchRequest} definition The search definition.
11760
+ * @returns Formatted URL.
11761
+ */
11762
+ function formatSearchQuery(definition) {
11763
+ const params = [];
11764
+ if (definition.fields) {
11765
+ params.push('_fields=' + definition.fields.join(','));
11766
+ }
11767
+ if (definition.filters) {
11768
+ definition.filters.forEach((filter) => params.push(formatFilter(filter)));
11769
+ }
11770
+ if (definition.sortRules && definition.sortRules.length > 0) {
11771
+ params.push(formatSortRules(definition.sortRules));
11772
+ }
11773
+ if (definition.offset !== undefined) {
11774
+ params.push('_offset=' + definition.offset);
11775
+ }
11776
+ if (definition.count !== undefined) {
11777
+ params.push('_count=' + definition.count);
11778
+ }
11779
+ if (definition.total !== undefined) {
11780
+ params.push('_total=' + definition.total);
11781
+ }
11782
+ if (params.length === 0) {
11783
+ return '';
11784
+ }
11785
+ params.sort();
11786
+ return '?' + params.join('&');
11787
+ }
11788
+ function formatFilter(filter) {
11789
+ const modifier = filter.operator in MODIFIER_OPERATORS ? ':' + filter.operator : '';
11790
+ const prefix = filter.operator !== exports.Operator.EQUALS && filter.operator in PREFIX_OPERATORS ? filter.operator : '';
11791
+ return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
11792
+ }
11793
+ function formatSortRules(sortRules) {
11794
+ return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
11795
+ }
11796
+
11797
+ const MAPPING_LANGUAGE_OPERATORS = [...FHIRPATH_OPERATORS, 'eq', 'ne', 'co'];
11798
+ function tokenize(str) {
11799
+ return new Tokenizer(str, FHIRPATH_KEYWORDS, MAPPING_LANGUAGE_OPERATORS, {
11800
+ dateTimeLiterals: true,
11801
+ symbolRegex: /[^\s\])]/,
11802
+ }).tokenize();
11803
+ }
11804
+
11805
+ // See: https://hl7.org/fhir/search_filter.html
11806
+ /**
11807
+ * The FhirFilterComparison class represents a comparison expression.
11808
+ */
11809
+ class FhirFilterComparison {
11810
+ constructor(path, operator, value) {
11811
+ this.path = path;
11812
+ this.operator = operator;
11813
+ this.value = value;
11814
+ }
11815
+ }
11816
+ /**
11817
+ * The FhirFilterNegation class represents a negation expression.
11818
+ * It contains a single child expression.
11819
+ */
11820
+ class FhirFilterNegation {
11821
+ constructor(child) {
11822
+ this.child = child;
11823
+ }
11824
+ }
11825
+ /**
11826
+ * The FhirFilterConnective class represents a connective expression.
11827
+ * It contains a list of child expressions.
11828
+ */
11829
+ class FhirFilterConnective {
11830
+ constructor(keyword, left, right) {
11831
+ this.keyword = keyword;
11832
+ this.left = left;
11833
+ this.right = right;
11834
+ }
11835
+ }
11836
+
11837
+ /**
11838
+ * The operatorMap maps FHIR _filter operators to Medplum search operators.
11839
+ * See _filter operators: https://www.hl7.org/fhir/search_filter.html#ops
11840
+ */
11841
+ const operatorMap = {
11842
+ // eq - an item in the set has an equal value
11843
+ eq: exports.Operator.EQUALS,
11844
+ // ne - An item in the set has an unequal value
11845
+ ne: exports.Operator.NOT_EQUALS,
11846
+ // co - An item in the set contains this value
11847
+ co: exports.Operator.CONTAINS,
11848
+ // sw - An item in the set starts with this value
11849
+ sw: undefined,
11850
+ // ew - An item in the set ends with this value
11851
+ ew: undefined,
11852
+ // gt / lt / ge / le - A value in the set is (greater than, less than, greater or equal, less or equal) the given value
11853
+ gt: exports.Operator.GREATER_THAN,
11854
+ lt: exports.Operator.LESS_THAN,
11855
+ ge: exports.Operator.GREATER_THAN_OR_EQUALS,
11856
+ le: exports.Operator.LESS_THAN_OR_EQUALS,
11857
+ // ap - A value in the set is approximately the same as this value.
11858
+ // 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
11859
+ ap: exports.Operator.APPROXIMATELY,
11860
+ // sa - The value starts after the specified value
11861
+ sa: exports.Operator.STARTS_AFTER,
11862
+ // eb - The value ends before the specified value
11863
+ eb: exports.Operator.ENDS_BEFORE,
11864
+ // pr - The set is empty or not (value is false or true)
11865
+ pr: exports.Operator.MISSING,
11866
+ // po - True if a (implied) date period in the set overlaps with the implied period in the value
11867
+ po: undefined,
11868
+ // ss - True if the value subsumes a concept in the set
11869
+ ss: undefined,
11870
+ // sb - True if the value is subsumed by a concept in the set
11871
+ sb: undefined,
11872
+ // in - True if one of the concepts is in the nominated value set by URI, either a relative, literal or logical vs
11873
+ in: exports.Operator.IN,
11874
+ // ni - True if none of the concepts are in the nominated value set by URI, either a relative, literal or logical vs
11875
+ ni: exports.Operator.NOT_IN,
11876
+ // re - True if one of the references in set points to the given URL
11877
+ re: undefined,
11878
+ // identifier - True if the identifier is in the identifier set (Medplum extension)
11879
+ identifier: exports.Operator.IDENTIFIER,
11880
+ };
11881
+ function getOperator(value) {
11882
+ const operator = operatorMap[value];
11883
+ if (!operator) {
11884
+ throw new OperationOutcomeError(badRequest('Invalid operator: ' + value));
11885
+ }
11886
+ return operator;
11887
+ }
11888
+ class FilterParameterParser {
11889
+ constructor(parser) {
11890
+ this.parser = parser;
11891
+ }
11892
+ parse() {
11893
+ let result;
11894
+ if (this.parser.peek()?.value === '(') {
11895
+ this.parser.consume('(');
11896
+ result = this.parse();
11897
+ this.parser.consume(')');
11898
+ }
11899
+ else if (this.parser.peek()?.value === 'not') {
11900
+ this.parser.consume('Symbol', 'not');
11901
+ this.parser.consume('(');
11902
+ result = new FhirFilterNegation(this.parse());
11903
+ this.parser.consume(')');
11904
+ }
11905
+ else {
11906
+ result = new FhirFilterComparison(this.parser.consume('Symbol').value, getOperator(this.parser.consume('Symbol').value), this.parser.consume().value);
11907
+ }
11908
+ const next = this.parser.peek()?.value;
11909
+ if (next === 'and' || next === 'or') {
11910
+ this.parser.consume('Symbol', next);
11911
+ return new FhirFilterConnective(next, result, this.parse());
11912
+ }
11913
+ return result;
11914
+ }
11915
+ }
11916
+ const fhirPathParserBuilder = initFhirPathParserBuilder();
11917
+ /**
11918
+ * Parses a FHIR _filter parameter expression into an AST.
11919
+ * @param input The FHIR _filter parameter expression.
11920
+ * @returns The AST representing the filters.
11921
+ */
11922
+ function parseFilterParameter(input) {
11923
+ const parser = fhirPathParserBuilder.construct(tokenize(input));
11924
+ parser.removeComments();
11925
+ return new FilterParameterParser(parser).parse();
11926
+ }
11927
+
11928
+ /**
11929
+ * The Hl7Context class represents the parsing context for an HL7 message.
11930
+ *
11931
+ * MSH-1:
11932
+ * https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.1
11933
+ *
11934
+ * MSH-2:
11935
+ * https://hl7-definition.caristix.com/v2/HL7v2.6/Fields/MSH.2
11936
+ *
11937
+ * See this tutorial on MSH, and why it's a bad idea to use anything other than the default values:
11938
+ * https://www.hl7soup.com/HL7TutorialMSH.html
11939
+ */
11940
+ class Hl7Context {
11941
+ constructor(segmentSeparator = '\r', fieldSeparator = '|', componentSeparator = '^', repetitionSeparator = '~', escapeCharacter = '\\', subcomponentSeparator = '&') {
11942
+ this.segmentSeparator = segmentSeparator;
11943
+ this.fieldSeparator = fieldSeparator;
11944
+ this.componentSeparator = componentSeparator;
11945
+ this.repetitionSeparator = repetitionSeparator;
11946
+ this.escapeCharacter = escapeCharacter;
11947
+ this.subcomponentSeparator = subcomponentSeparator;
11948
+ }
11949
+ /**
11950
+ * Returns the MSH-2 field value based on the configured separators.
11951
+ * @returns The HL7 MSH-2 field value.
11952
+ */
11953
+ getMsh2() {
11954
+ return (this.fieldSeparator +
11955
+ this.componentSeparator +
11956
+ this.repetitionSeparator +
11957
+ this.escapeCharacter +
11958
+ this.subcomponentSeparator);
11959
+ }
11960
+ }
11961
+ /**
11962
+ * The Hl7Message class represents one HL7 message.
11963
+ * A message is a collection of segments.
11964
+ */
11965
+ class Hl7Message {
11966
+ /**
11967
+ * Creates a new HL7 message.
11968
+ * @param segments The HL7 segments.
11969
+ * @param context Optional HL7 parsing context.
11970
+ */
11971
+ constructor(segments, context = new Hl7Context()) {
11972
+ this.context = context;
11973
+ this.segments = segments;
11974
+ }
11975
+ /**
11976
+ * Returns an HL7 segment by index or by name.
11977
+ * @param index The HL7 segment index or name.
11978
+ * @returns The HL7 segment if found; otherwise, undefined.
11979
+ */
11980
+ get(index) {
11981
+ if (typeof index === 'number') {
11982
+ return this.segments[index];
11983
+ }
11984
+ return this.segments.find((s) => s.name === index);
11985
+ }
11986
+ /**
11987
+ * Returns all HL7 segments of a given name.
11988
+ * @param name The HL7 segment name.
11989
+ * @returns An array of HL7 segments with the specified name.
11990
+ */
11991
+ getAll(name) {
11992
+ return this.segments.filter((s) => s.name === name);
11993
+ }
11994
+ /**
11995
+ * Returns the HL7 message as a string.
11996
+ * @returns The HL7 message as a string.
11997
+ */
11529
11998
  toString() {
11530
11999
  return this.segments.map((s) => s.toString()).join(this.context.segmentSeparator);
11531
12000
  }
@@ -11671,6 +12140,28 @@
11671
12140
  return new Hl7Field(text.split(context.repetitionSeparator).map((r) => r.split(context.componentSeparator)), context);
11672
12141
  }
11673
12142
  }
12143
+ /**
12144
+ * Returns a formatted string representing the date in ISO-8601 format.
12145
+ * @param hl7Date Date string.
12146
+ * @param options Optional configuration Object
12147
+ * @returns
12148
+ */
12149
+ function parseHl7Date(hl7Date, options) {
12150
+ if (!hl7Date) {
12151
+ return undefined;
12152
+ }
12153
+ options = { ...{ seconds: true, tzOffset: 'Z' }, ...options };
12154
+ const year = Number.parseInt(hl7Date.substring(0, 4));
12155
+ const month = Number.parseInt(hl7Date.substring(4, 6));
12156
+ const date = Number.parseInt(hl7Date.substring(6, 8));
12157
+ const hours = Number.parseInt(hl7Date.substring(8, 10));
12158
+ const minutes = Number.parseInt(hl7Date.substring(10, 12));
12159
+ const seconds = options.seconds ? Number.parseInt(hl7Date.substring(12, 14)) : 0;
12160
+ return `${pad2(year)}-${pad2(month)}-${pad2(date)}T${pad2(hours)}:${pad2(minutes)}:${pad2(seconds)}.000${options.tzOffset}`;
12161
+ }
12162
+ function pad2(n) {
12163
+ return n.toString().padStart(2, '0');
12164
+ }
11674
12165
 
11675
12166
  /*
11676
12167
  * This file provides schema validation utilities for FHIR JSON objects.
@@ -12094,6 +12585,7 @@
12094
12585
  SearchParameterType["QUANTITY"] = "QUANTITY";
12095
12586
  SearchParameterType["TEXT"] = "TEXT";
12096
12587
  SearchParameterType["REFERENCE"] = "REFERENCE";
12588
+ SearchParameterType["CANONICAL"] = "CANONICAL";
12097
12589
  SearchParameterType["DATE"] = "DATE";
12098
12590
  SearchParameterType["DATETIME"] = "DATETIME";
12099
12591
  SearchParameterType["PERIOD"] = "PERIOD";
@@ -12147,7 +12639,7 @@
12147
12639
  if (!elementDefinition) {
12148
12640
  throw new Error(`Element definition not found for ${resourceType} ${searchParam.code}`);
12149
12641
  }
12150
- if (elementDefinition.max === '*') {
12642
+ if (elementDefinition.max !== '0' && elementDefinition.max !== '1') {
12151
12643
  array = true;
12152
12644
  }
12153
12645
  propertyType = elementDefinition.type?.[0].code;
@@ -12165,394 +12657,86 @@
12165
12657
  }
12166
12658
  }
12167
12659
  }
12168
- const type = getSearchParameterType(searchParam, propertyType);
12169
- const result = { columnName, type, elementDefinition, array };
12170
- setSearchParamterDetails(resourceType, code, result);
12171
- return result;
12172
- }
12173
- function isBackboneElement(propertyType) {
12174
- return propertyType === 'Element' || propertyType === 'BackboneElement';
12175
- }
12176
- /**
12177
- * Converts a hyphen-delimited code to camelCase string.
12178
- * @param code The search parameter code.
12179
- * @returns The SQL column name.
12180
- */
12181
- function convertCodeToColumnName(code) {
12182
- return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
12183
- }
12184
- function getSearchParameterType(searchParam, propertyType) {
12185
- let type = exports.SearchParameterType.TEXT;
12186
- switch (searchParam.type) {
12187
- case 'date':
12188
- if (propertyType === exports.PropertyType.dateTime || propertyType === exports.PropertyType.instant) {
12189
- type = exports.SearchParameterType.DATETIME;
12190
- }
12191
- else {
12192
- type = exports.SearchParameterType.DATE;
12193
- }
12194
- break;
12195
- case 'number':
12196
- type = exports.SearchParameterType.NUMBER;
12197
- break;
12198
- case 'quantity':
12199
- type = exports.SearchParameterType.QUANTITY;
12200
- break;
12201
- case 'reference':
12202
- type = exports.SearchParameterType.REFERENCE;
12203
- break;
12204
- case 'token':
12205
- if (propertyType === 'boolean') {
12206
- type = exports.SearchParameterType.BOOLEAN;
12207
- }
12208
- break;
12209
- }
12210
- return type;
12211
- }
12212
- function getExpressionForResourceType(resourceType, expression) {
12213
- const expressions = expression.split(' | ');
12214
- for (const e of expressions) {
12215
- if (isIgnoredExpression(e)) {
12216
- continue;
12217
- }
12218
- const simplified = simplifyExpression(e);
12219
- if (simplified.startsWith(resourceType + '.')) {
12220
- return simplified;
12221
- }
12222
- }
12223
- return undefined;
12224
- }
12225
- function isIgnoredExpression(input) {
12226
- return input.includes(' as Period') || input.includes(' as SampledDate');
12227
- }
12228
- function simplifyExpression(input) {
12229
- let result = input.trim();
12230
- if (result.startsWith('(') && result.endsWith(')')) {
12231
- result = result.substring(1, result.length - 1);
12232
- }
12233
- if (result.includes('[0]')) {
12234
- result = result.replaceAll('[0]', '');
12235
- }
12236
- const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];
12237
- for (const stopString of stopStrings) {
12238
- if (result.includes(stopString)) {
12239
- result = result.substring(0, result.indexOf(stopString));
12240
- }
12241
- }
12242
- return result;
12243
- }
12244
-
12245
- const DEFAULT_SEARCH_COUNT = 20;
12246
- /**
12247
- * Search operators.
12248
- * These operators represent "modifiers" and "prefixes" in FHIR search.
12249
- * See: https://www.hl7.org/fhir/search.html
12250
- */
12251
- exports.Operator = void 0;
12252
- (function (Operator) {
12253
- Operator["EQUALS"] = "eq";
12254
- Operator["NOT_EQUALS"] = "ne";
12255
- // Numbers
12256
- Operator["GREATER_THAN"] = "gt";
12257
- Operator["LESS_THAN"] = "lt";
12258
- Operator["GREATER_THAN_OR_EQUALS"] = "ge";
12259
- Operator["LESS_THAN_OR_EQUALS"] = "le";
12260
- // Dates
12261
- Operator["STARTS_AFTER"] = "sa";
12262
- Operator["ENDS_BEFORE"] = "eb";
12263
- Operator["APPROXIMATELY"] = "ap";
12264
- // String
12265
- Operator["CONTAINS"] = "contains";
12266
- Operator["EXACT"] = "exact";
12267
- // Token
12268
- Operator["TEXT"] = "text";
12269
- Operator["NOT"] = "not";
12270
- Operator["ABOVE"] = "above";
12271
- Operator["BELOW"] = "below";
12272
- Operator["IN"] = "in";
12273
- Operator["NOT_IN"] = "not-in";
12274
- Operator["OF_TYPE"] = "of-type";
12275
- // All
12276
- Operator["MISSING"] = "missing";
12277
- // Reference
12278
- Operator["IDENTIFIER"] = "identifier";
12279
- })(exports.Operator || (exports.Operator = {}));
12280
- /**
12281
- * Parameter names may specify a modifier as a suffix.
12282
- * The modifiers are separated from the parameter name by a colon.
12283
- * See: https://www.hl7.org/fhir/search.html#modifiers
12284
- */
12285
- const MODIFIER_OPERATORS = {
12286
- contains: exports.Operator.CONTAINS,
12287
- exact: exports.Operator.EXACT,
12288
- above: exports.Operator.ABOVE,
12289
- below: exports.Operator.BELOW,
12290
- text: exports.Operator.TEXT,
12291
- not: exports.Operator.NOT,
12292
- in: exports.Operator.IN,
12293
- 'not-in': exports.Operator.NOT_IN,
12294
- 'of-type': exports.Operator.OF_TYPE,
12295
- missing: exports.Operator.MISSING,
12296
- identifier: exports.Operator.IDENTIFIER,
12297
- };
12298
- /**
12299
- * For the ordered parameter types of number, date, and quantity,
12300
- * a prefix to the parameter value may be used to control the nature
12301
- * of the matching.
12302
- * See: https://www.hl7.org/fhir/search.html#prefix
12303
- */
12304
- const PREFIX_OPERATORS = {
12305
- eq: exports.Operator.EQUALS,
12306
- ne: exports.Operator.NOT_EQUALS,
12307
- lt: exports.Operator.LESS_THAN,
12308
- le: exports.Operator.LESS_THAN_OR_EQUALS,
12309
- gt: exports.Operator.GREATER_THAN,
12310
- ge: exports.Operator.GREATER_THAN_OR_EQUALS,
12311
- sa: exports.Operator.STARTS_AFTER,
12312
- eb: exports.Operator.ENDS_BEFORE,
12313
- ap: exports.Operator.APPROXIMATELY,
12314
- };
12315
- /**
12316
- * Parses a search URL into a search request.
12317
- * @param resourceType The FHIR resource type.
12318
- * @param query The collection of query string parameters.
12319
- * @returns A parsed SearchRequest.
12320
- */
12321
- function parseSearchRequest(resourceType, query) {
12322
- const queryArray = [];
12323
- for (const [key, value] of Object.entries(query)) {
12324
- if (Array.isArray(value)) {
12325
- for (let i = 0; i < value.length; i++) {
12326
- queryArray.push([key, value[i]]);
12327
- }
12328
- }
12329
- else {
12330
- queryArray.push([key, value || '']);
12331
- }
12332
- }
12333
- return parseSearchImpl(resourceType, queryArray);
12660
+ const type = getSearchParameterType(searchParam, propertyType);
12661
+ const result = { columnName, type, elementDefinition, array };
12662
+ setSearchParamterDetails(resourceType, code, result);
12663
+ return result;
12334
12664
  }
12335
- /**
12336
- * Parses a search URL into a search request.
12337
- * @param url The search URL.
12338
- * @returns A parsed SearchRequest.
12339
- */
12340
- function parseSearchUrl(url) {
12341
- const resourceType = url.pathname.split('/').filter(Boolean).pop();
12342
- return parseSearchImpl(resourceType, url.searchParams.entries());
12665
+ function isBackboneElement(propertyType) {
12666
+ return propertyType === 'Element' || propertyType === 'BackboneElement';
12343
12667
  }
12344
12668
  /**
12345
- * Parses a URL string into a SearchRequest.
12346
- * @param url The URL to parse.
12347
- * @returns Parsed search definition.
12669
+ * Converts a hyphen-delimited code to camelCase string.
12670
+ * @param code The search parameter code.
12671
+ * @returns The SQL column name.
12348
12672
  */
12349
- function parseSearchDefinition(url) {
12350
- return parseSearchUrl(new URL(url, 'https://example.com/'));
12351
- }
12352
- function parseSearchImpl(resourceType, query) {
12353
- const searchRequest = {
12354
- resourceType,
12355
- };
12356
- for (const [key, value] of query) {
12357
- parseKeyValue(searchRequest, key, value);
12358
- }
12359
- return searchRequest;
12673
+ function convertCodeToColumnName(code) {
12674
+ return code.split('-').reduce((result, word, index) => result + (index ? capitalize(word) : word), '');
12360
12675
  }
12361
- function parseKeyValue(searchRequest, key, value) {
12362
- let code;
12363
- let modifier;
12364
- const colonIndex = key.indexOf(':');
12365
- if (colonIndex >= 0) {
12366
- code = key.substring(0, colonIndex);
12367
- modifier = key.substring(colonIndex + 1);
12368
- }
12369
- else {
12370
- code = key;
12371
- modifier = '';
12372
- }
12373
- switch (code) {
12374
- case '_sort':
12375
- parseSortRule(searchRequest, value);
12376
- break;
12377
- case '_count':
12378
- searchRequest.count = parseInt(value);
12379
- break;
12380
- case '_offset':
12381
- searchRequest.offset = parseInt(value);
12382
- break;
12383
- case '_total':
12384
- searchRequest.total = value;
12385
- break;
12386
- case '_summary':
12387
- searchRequest.total = 'estimate';
12388
- searchRequest.count = 0;
12389
- break;
12390
- case '_include':
12391
- searchRequest.include = value;
12676
+ function getSearchParameterType(searchParam, propertyType) {
12677
+ let type = exports.SearchParameterType.TEXT;
12678
+ switch (searchParam.type) {
12679
+ case 'date':
12680
+ if (propertyType === exports.PropertyType.date) {
12681
+ type = exports.SearchParameterType.DATE;
12682
+ }
12683
+ else {
12684
+ type = exports.SearchParameterType.DATETIME;
12685
+ }
12392
12686
  break;
12393
- case '_revinclude':
12394
- searchRequest.revInclude = value;
12687
+ case 'number':
12688
+ type = exports.SearchParameterType.NUMBER;
12395
12689
  break;
12396
- case '_fields':
12397
- searchRequest.fields = value.split(',');
12690
+ case 'quantity':
12691
+ type = exports.SearchParameterType.QUANTITY;
12398
12692
  break;
12399
- default: {
12400
- const param = globalSchema.types[searchRequest.resourceType]?.searchParams?.[code];
12401
- if (param) {
12402
- parseParameter(searchRequest, param, modifier, value);
12693
+ case 'reference':
12694
+ if (propertyType === exports.PropertyType.canonical) {
12695
+ type = exports.SearchParameterType.CANONICAL;
12403
12696
  }
12404
12697
  else {
12405
- parseUnknownParameter(searchRequest, code, modifier, value);
12698
+ type = exports.SearchParameterType.REFERENCE;
12406
12699
  }
12407
- }
12408
- }
12409
- }
12410
- function parseSortRule(searchRequest, value) {
12411
- for (const field of value.split(',')) {
12412
- let code;
12413
- let descending = false;
12414
- if (field.startsWith('-')) {
12415
- code = field.substring(1);
12416
- descending = true;
12417
- }
12418
- else {
12419
- code = field;
12420
- }
12421
- if (!searchRequest.sortRules) {
12422
- searchRequest.sortRules = [];
12423
- }
12424
- searchRequest.sortRules.push({ code, descending });
12425
- }
12426
- }
12427
- function parseParameter(searchRequest, searchParam, modifier, value) {
12428
- if (modifier === 'missing') {
12429
- addFilter(searchRequest, {
12430
- code: searchParam.code,
12431
- operator: exports.Operator.MISSING,
12432
- value,
12433
- });
12434
- return;
12435
- }
12436
- switch (searchParam.type) {
12437
- case 'number':
12438
- case 'date':
12439
- parsePrefixType(searchRequest, searchParam, value);
12440
12700
  break;
12441
- case 'reference':
12442
- case 'string':
12443
12701
  case 'token':
12444
- case 'uri':
12445
- parseModifierType(searchRequest, searchParam, modifier, value);
12446
- break;
12447
- case 'quantity':
12448
- parseQuantity(searchRequest, searchParam, value);
12702
+ if (propertyType === 'boolean') {
12703
+ type = exports.SearchParameterType.BOOLEAN;
12704
+ }
12449
12705
  break;
12450
12706
  }
12707
+ return type;
12451
12708
  }
12452
- function parsePrefixType(searchRequest, param, input) {
12453
- const { operator, value } = parsePrefix(input);
12454
- addFilter(searchRequest, {
12455
- code: param.code,
12456
- operator,
12457
- value,
12458
- });
12459
- }
12460
- function parseModifierType(searchRequest, param, modifier, value) {
12461
- addFilter(searchRequest, {
12462
- code: param.code,
12463
- operator: parseModifier(modifier),
12464
- value,
12465
- });
12466
- }
12467
- function parseQuantity(searchRequest, param, input) {
12468
- const [prefixNumber, unitSystem, unitCode] = input.split('|');
12469
- const { operator, value } = parsePrefix(prefixNumber);
12470
- addFilter(searchRequest, {
12471
- code: param.code,
12472
- operator,
12473
- value,
12474
- unitSystem,
12475
- unitCode,
12476
- });
12477
- }
12478
- function parseUnknownParameter(searchRequest, code, modifier, value) {
12479
- let operator = exports.Operator.EQUALS;
12480
- if (modifier) {
12481
- operator = modifier;
12482
- }
12483
- else if (value.length >= 2) {
12484
- const prefix = value.substring(0, 2);
12485
- if (prefix in PREFIX_OPERATORS) {
12486
- if (value.length === 2 || value.at(2)?.match(/\d/)) {
12487
- operator = prefix;
12488
- value = value.substring(prefix.length);
12489
- }
12709
+ function getExpressionForResourceType(resourceType, expression) {
12710
+ const expressions = expression.split(' | ');
12711
+ for (const e of expressions) {
12712
+ if (isIgnoredExpression(e)) {
12713
+ continue;
12714
+ }
12715
+ const simplified = simplifyExpression(e);
12716
+ if (simplified.startsWith(resourceType + '.')) {
12717
+ return simplified;
12490
12718
  }
12491
12719
  }
12492
- addFilter(searchRequest, {
12493
- code,
12494
- operator,
12495
- value,
12496
- });
12497
- }
12498
- function parsePrefix(input) {
12499
- const prefix = input.substring(0, 2);
12500
- const prefixOperator = PREFIX_OPERATORS[prefix];
12501
- if (prefixOperator) {
12502
- return { operator: prefixOperator, value: input.substring(2) };
12503
- }
12504
- return { operator: exports.Operator.EQUALS, value: input };
12505
- }
12506
- function parseModifier(modifier) {
12507
- return MODIFIER_OPERATORS[modifier] || exports.Operator.EQUALS;
12720
+ return undefined;
12508
12721
  }
12509
- function addFilter(searchRequest, filter) {
12510
- if (searchRequest.filters) {
12511
- searchRequest.filters.push(filter);
12512
- }
12513
- else {
12514
- searchRequest.filters = [filter];
12515
- }
12722
+ function isIgnoredExpression(input) {
12723
+ return input.includes(' as Period') || input.includes(' as SampledDate');
12516
12724
  }
12517
- /**
12518
- * Formats a search definition object into a query string.
12519
- * Note: The return value does not include the resource type.
12520
- * @param {!SearchRequest} definition The search definition.
12521
- * @returns Formatted URL.
12522
- */
12523
- function formatSearchQuery(definition) {
12524
- const params = [];
12525
- if (definition.fields) {
12526
- params.push('_fields=' + definition.fields.join(','));
12527
- }
12528
- if (definition.filters) {
12529
- definition.filters.forEach((filter) => params.push(formatFilter(filter)));
12530
- }
12531
- if (definition.sortRules && definition.sortRules.length > 0) {
12532
- params.push(formatSortRules(definition.sortRules));
12533
- }
12534
- if (definition.offset !== undefined) {
12535
- params.push('_offset=' + definition.offset);
12536
- }
12537
- if (definition.count !== undefined) {
12538
- params.push('_count=' + definition.count);
12725
+ function simplifyExpression(input) {
12726
+ let result = input.trim();
12727
+ if (result.startsWith('(') && result.endsWith(')')) {
12728
+ result = result.substring(1, result.length - 1);
12539
12729
  }
12540
- if (definition.total !== undefined) {
12541
- params.push('_total=' + definition.total);
12730
+ if (result.includes('[0]')) {
12731
+ result = result.replaceAll('[0]', '');
12542
12732
  }
12543
- if (params.length === 0) {
12544
- return '';
12733
+ const stopStrings = [' != ', ' as ', '.as(', '.exists(', '.resolve(', '.where('];
12734
+ for (const stopString of stopStrings) {
12735
+ if (result.includes(stopString)) {
12736
+ result = result.substring(0, result.indexOf(stopString));
12737
+ }
12545
12738
  }
12546
- params.sort();
12547
- return '?' + params.join('&');
12548
- }
12549
- function formatFilter(filter) {
12550
- const modifier = filter.operator in MODIFIER_OPERATORS ? ':' + filter.operator : '';
12551
- const prefix = filter.operator !== exports.Operator.EQUALS && filter.operator in PREFIX_OPERATORS ? filter.operator : '';
12552
- return `${filter.code}${modifier}=${prefix}${encodeURIComponent(filter.value)}`;
12553
- }
12554
- function formatSortRules(sortRules) {
12555
- return '_sort=' + sortRules.map((sr) => (sr.descending ? '-' + sr.code : sr.code)).join(',');
12739
+ return result;
12556
12740
  }
12557
12741
 
12558
12742
  /**
@@ -12710,6 +12894,29 @@
12710
12894
  return operator === exports.Operator.NOT_EQUALS || operator === exports.Operator.NOT;
12711
12895
  }
12712
12896
 
12897
+ /**
12898
+ * Reads data from a Readable stream and returns a Promise that resolves with a Buffer containing all the data.
12899
+ * @param stream - The Readable stream to read from.
12900
+ * @returns A Promise that resolves with a Buffer containing all the data from the Readable stream.
12901
+ */
12902
+ function streamToBuffer(stream) {
12903
+ const chunks = [];
12904
+ return new Promise((resolve, reject) => {
12905
+ stream.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
12906
+ stream.on('error', (err) => {
12907
+ console.error(err.message);
12908
+ stream.destroy();
12909
+ reject(err);
12910
+ });
12911
+ stream.on('end', () => {
12912
+ resolve(Buffer.concat(chunks));
12913
+ });
12914
+ stream.on('close', () => {
12915
+ stream.destroy();
12916
+ });
12917
+ });
12918
+ }
12919
+
12713
12920
  exports.AndAtom = AndAtom;
12714
12921
  exports.ArithemticOperatorAtom = ArithemticOperatorAtom;
12715
12922
  exports.AsAtom = AsAtom;
@@ -12783,6 +12990,7 @@
12783
12990
  exports.fhirPathNot = fhirPathNot;
12784
12991
  exports.findObservationInterval = findObservationInterval;
12785
12992
  exports.findObservationReferenceRange = findObservationReferenceRange;
12993
+ exports.findResourceByCode = findResourceByCode;
12786
12994
  exports.forbidden = forbidden;
12787
12995
  exports.formatAddress = formatAddress;
12788
12996
  exports.formatCodeableConcept = formatCodeableConcept;
@@ -12853,6 +13061,7 @@
12853
13061
  exports.operationOutcomeToString = operationOutcomeToString;
12854
13062
  exports.parseFhirPath = parseFhirPath;
12855
13063
  exports.parseFilterParameter = parseFilterParameter;
13064
+ exports.parseHl7Date = parseHl7Date;
12856
13065
  exports.parseJWTPayload = parseJWTPayload;
12857
13066
  exports.parseMappingLanguage = parseMappingLanguage;
12858
13067
  exports.parseSearchDefinition = parseSearchDefinition;
@@ -12867,6 +13076,7 @@
12867
13076
  exports.removeDuplicates = removeDuplicates;
12868
13077
  exports.resolveId = resolveId;
12869
13078
  exports.setCodeBySystem = setCodeBySystem;
13079
+ exports.streamToBuffer = streamToBuffer;
12870
13080
  exports.stringify = stringify;
12871
13081
  exports.toJsBoolean = toJsBoolean;
12872
13082
  exports.toTypedValue = toTypedValue;